Browse Source

Merge pull request #261 from AtomicGameEngine/JME-ATOMIC-SYSTEMUI

Add SystemUI which is available regardless of theme, resurrect profiling and console views
JoshEngebretson 10 years ago
parent
commit
c28adea258
62 changed files with 16595 additions and 61 deletions
  1. 1 1
      CMakeLists.txt
  2. 31 3
      Script/AtomicEditor/ui/frames/menus/MainFrameMenu.ts
  3. 4 3
      Source/Atomic/CMakeLists.txt
  4. 0 0
      Source/Atomic/Engine/Console.h
  5. 0 0
      Source/Atomic/Engine/DebugHud.h
  6. 0 2
      Source/Atomic/Engine/Engine.cpp
  7. 218 0
      Source/Atomic/UI/SystemUI/BorderImage.cpp
  8. 121 0
      Source/Atomic/UI/SystemUI/BorderImage.h
  9. 195 0
      Source/Atomic/UI/SystemUI/Button.cpp
  10. 114 0
      Source/Atomic/UI/SystemUI/Button.h
  11. 117 0
      Source/Atomic/UI/SystemUI/CheckBox.cpp
  12. 77 0
      Source/Atomic/UI/SystemUI/CheckBox.h
  13. 28 28
      Source/Atomic/UI/SystemUI/Console.cpp
  14. 176 0
      Source/Atomic/UI/SystemUI/Console.h
  15. 312 0
      Source/Atomic/UI/SystemUI/Cursor.cpp
  16. 151 0
      Source/Atomic/UI/SystemUI/Cursor.h
  17. 17 16
      Source/Atomic/UI/SystemUI/DebugHud.cpp
  18. 133 0
      Source/Atomic/UI/SystemUI/DebugHud.h
  19. 346 0
      Source/Atomic/UI/SystemUI/DropDownList.cpp
  20. 127 0
      Source/Atomic/UI/SystemUI/DropDownList.h
  21. 236 0
      Source/Atomic/UI/SystemUI/Font.cpp
  22. 114 0
      Source/Atomic/UI/SystemUI/Font.h
  23. 130 0
      Source/Atomic/UI/SystemUI/FontFace.cpp
  24. 124 0
      Source/Atomic/UI/SystemUI/FontFace.h
  25. 391 0
      Source/Atomic/UI/SystemUI/FontFaceBitmap.cpp
  26. 68 0
      Source/Atomic/UI/SystemUI/FontFaceBitmap.h
  27. 459 0
      Source/Atomic/UI/SystemUI/FontFaceFreeType.cpp
  28. 78 0
      Source/Atomic/UI/SystemUI/FontFaceFreeType.h
  29. 669 0
      Source/Atomic/UI/SystemUI/LineEdit.cpp
  30. 172 0
      Source/Atomic/UI/SystemUI/LineEdit.h
  31. 1144 0
      Source/Atomic/UI/SystemUI/ListView.cpp
  32. 195 0
      Source/Atomic/UI/SystemUI/ListView.h
  33. 444 0
      Source/Atomic/UI/SystemUI/Menu.cpp
  34. 117 0
      Source/Atomic/UI/SystemUI/Menu.h
  35. 150 0
      Source/Atomic/UI/SystemUI/MessageBox.cpp
  36. 82 0
      Source/Atomic/UI/SystemUI/MessageBox.h
  37. 329 0
      Source/Atomic/UI/SystemUI/ScrollBar.cpp
  38. 137 0
      Source/Atomic/UI/SystemUI/ScrollBar.h
  39. 709 0
      Source/Atomic/UI/SystemUI/ScrollView.cpp
  40. 199 0
      Source/Atomic/UI/SystemUI/ScrollView.h
  41. 287 0
      Source/Atomic/UI/SystemUI/Slider.cpp
  42. 126 0
      Source/Atomic/UI/SystemUI/Slider.h
  43. 287 0
      Source/Atomic/UI/SystemUI/Sprite.cpp
  44. 130 0
      Source/Atomic/UI/SystemUI/Sprite.h
  45. 1815 0
      Source/Atomic/UI/SystemUI/SystemUI.cpp
  46. 347 0
      Source/Atomic/UI/SystemUI/SystemUI.h
  47. 334 0
      Source/Atomic/UI/SystemUI/SystemUIBatch.cpp
  48. 99 0
      Source/Atomic/UI/SystemUI/SystemUIBatch.h
  49. 389 0
      Source/Atomic/UI/SystemUI/SystemUIEvents.h
  50. 770 0
      Source/Atomic/UI/SystemUI/Text.cpp
  51. 268 0
      Source/Atomic/UI/SystemUI/Text.h
  52. 113 0
      Source/Atomic/UI/SystemUI/ToolTip.cpp
  53. 71 0
      Source/Atomic/UI/SystemUI/ToolTip.h
  54. 2034 0
      Source/Atomic/UI/SystemUI/UIElement.cpp
  55. 722 0
      Source/Atomic/UI/SystemUI/UIElement.h
  56. 427 0
      Source/Atomic/UI/SystemUI/Window.cpp
  57. 173 0
      Source/Atomic/UI/SystemUI/Window.h
  58. 62 1
      Source/Atomic/UI/UI.cpp
  59. 8 0
      Source/Atomic/UI/UI.h
  60. 7 7
      Source/Atomic/UI/UIInput.cpp
  61. 6 0
      Source/AtomicEditor/EditorMode/AEEditorMode.cpp
  62. 5 0
      Source/AtomicEditor/PlayerMode/AEPlayerMode.cpp

+ 1 - 1
CMakeLists.txt

@@ -9,7 +9,7 @@ include(AtomicUtils)
 
 
 add_definitions(-DATOMIC_ROOT_SOURCE_DIR="${CMAKE_SOURCE_DIR}" -DATOMIC_ROOT_BUILD_DIR="${CMAKE_BINARY_DIR}")
 add_definitions(-DATOMIC_ROOT_SOURCE_DIR="${CMAKE_SOURCE_DIR}" -DATOMIC_ROOT_BUILD_DIR="${CMAKE_BINARY_DIR}")
 
 
-add_definitions( -DATOMIC_API= -DATOMIC_STATIC_DEFINE -DATOMIC_ATOMIC2D -DATOMIC_LOGGING)
+add_definitions( -DATOMIC_API= -DATOMIC_STATIC_DEFINE -DATOMIC_ATOMIC2D -DATOMIC_LOGGING -DATOMIC_PROFILING)
 
 
 if (NOT DEFINED ATOMIC_DEV_BUILD)
 if (NOT DEFINED ATOMIC_DEV_BUILD)
     set(ATOMIC_DEV_BUILD 1)
     set(ATOMIC_DEV_BUILD 1)

+ 31 - 3
Script/AtomicEditor/ui/frames/menus/MainFrameMenu.ts

@@ -13,6 +13,8 @@ class MainFrameMenu extends Atomic.ScriptObject {
         MenuItemSources.createMenuItemSource("menu atomic editor", editorItems);
         MenuItemSources.createMenuItemSource("menu atomic editor", editorItems);
         MenuItemSources.createMenuItemSource("menu edit", editItems);
         MenuItemSources.createMenuItemSource("menu edit", editItems);
         MenuItemSources.createMenuItemSource("menu file", fileItems);
         MenuItemSources.createMenuItemSource("menu file", fileItems);
+        MenuItemSources.createMenuItemSource("menu tools", toolsItems);
+        MenuItemSources.createMenuItemSource("menu developer", developerItems);
     }
     }
 
 
     handlePopupMenu(target: Atomic.UIWidget, refid: string): boolean {
     handlePopupMenu(target: Atomic.UIWidget, refid: string): boolean {
@@ -28,9 +30,9 @@ class MainFrameMenu extends Atomic.ScriptObject {
 
 
             if (refid == "manage license") {
             if (refid == "manage license") {
 
 
-              EditorUI.getModelOps().showManageLicense();
+                EditorUI.getModelOps().showManageLicense();
 
 
-              return true;
+                return true;
 
 
             }
             }
 
 
@@ -119,8 +121,22 @@ class MainFrameMenu extends Atomic.ScriptObject {
                 return true;
                 return true;
             }
             }
 
 
-
             return false;
             return false;
+
+        } else if (target.id == "menu developer popup") {
+
+            if (refid == "developer show console") {
+                Atomic.ui.showConsole(true);
+                return true;
+            }
+
+        } else if (target.id == "menu tools popup") {
+
+            if (refid == "tools toggle profiler") {
+                Atomic.ui.toggleDebugHud();
+                return true;
+            }
+
         }
         }
 
 
     }
     }
@@ -163,6 +179,18 @@ var editItems = {
 
 
 };
 };
 
 
+var toolsItems = {
+
+    "Toggle Profiler": ["tools toggle profiler"]
+
+}
+
+var developerItems = {
+
+    "Show Console": ["developer show console"]
+
+}
+
 var fileItems = {
 var fileItems = {
 
 
     "New Project": ["file new project"],
     "New Project": ["file new project"],

+ 4 - 3
Source/Atomic/CMakeLists.txt

@@ -23,6 +23,7 @@ endif()
 file (GLOB ATOMIC2D_SOURCE Atomic2D/*.cpp Atomic2D/*.h)
 file (GLOB ATOMIC2D_SOURCE Atomic2D/*.cpp Atomic2D/*.h)
 file (GLOB SCENE_SOURCE Scene/*.cpp Scene/*.h)
 file (GLOB SCENE_SOURCE Scene/*.cpp Scene/*.h)
 file (GLOB UI_SOURCE UI/*.cpp UI/*.h)
 file (GLOB UI_SOURCE UI/*.cpp UI/*.h)
+file (GLOB SYSTEM_UI_SOURCE UI/SystemUI/*.cpp UI/SystemUI/*.h)
 
 
 if (NOT ATOMIC_BUILD_2D)
 if (NOT ATOMIC_BUILD_2D)
   file (GLOB ATOMIC3D_SOURCE Atomic3D/*.cpp Atomic3D/*.h)
   file (GLOB ATOMIC3D_SOURCE Atomic3D/*.cpp Atomic3D/*.h)
@@ -34,11 +35,11 @@ endif()
 file (GLOB GRAPHICS_SOURCE Graphics/*.cpp Graphics/*.h)
 file (GLOB GRAPHICS_SOURCE Graphics/*.cpp Graphics/*.h)
 
 
 if (MSVC)
 if (MSVC)
-    if (ATOMIC_D3D11)  
+    if (ATOMIC_D3D11)
       file (GLOB GRAPHICS_IMPL_SOURCE Graphics/Direct3D11/*.cpp Graphics/Direct3D11/*.h)
       file (GLOB GRAPHICS_IMPL_SOURCE Graphics/Direct3D11/*.cpp Graphics/Direct3D11/*.h)
     else()
     else()
       file (GLOB GRAPHICS_IMPL_SOURCE Graphics/Direct3D9/*.cpp Graphics/Direct3D9/*.h)
       file (GLOB GRAPHICS_IMPL_SOURCE Graphics/Direct3D9/*.cpp Graphics/Direct3D9/*.h)
-    endif()  
+    endif()
 else()
 else()
 
 
 # for kNet
 # for kNet
@@ -67,7 +68,7 @@ set (SOURCE_FILES ${CONTAINER_SOURCE} ${CORE_SOURCE} ${ENGINE_SOURCE} ${INPUT_SO
                   ${GRAPHICS_SOURCE} ${GRAPHICS_IMPL_SOURCE}
                   ${GRAPHICS_SOURCE} ${GRAPHICS_IMPL_SOURCE}
                   ${ATOMIC3D_SOURCE}
                   ${ATOMIC3D_SOURCE}
                   ${ATOMIC2D_SOURCE} ${ENVIRONMENT_SOURCE}
                   ${ATOMIC2D_SOURCE} ${ENVIRONMENT_SOURCE}
-                  ${SCENE_SOURCE} ${UI_SOURCE}
+                  ${SCENE_SOURCE} ${UI_SOURCE} ${SYSTEM_UI_SOURCE}
                   ${PLATFORM_SOURCE})
                   ${PLATFORM_SOURCE})
 
 
 if (NOT EMSCRIPTEN)
 if (NOT EMSCRIPTEN)

+ 0 - 0
Source/Atomic/Engine/Console.h


+ 0 - 0
Source/Atomic/Engine/DebugHud.h


+ 0 - 2
Source/Atomic/Engine/Engine.cpp

@@ -28,8 +28,6 @@
 #include "../Core/ProcessUtils.h"
 #include "../Core/ProcessUtils.h"
 #include "../Core/Profiler.h"
 #include "../Core/Profiler.h"
 #include "../Core/WorkQueue.h"
 #include "../Core/WorkQueue.h"
-#include "../Engine/Console.h"
-#include "../Engine/DebugHud.h"
 #include "../Engine/Engine.h"
 #include "../Engine/Engine.h"
 #include "../Graphics/Graphics.h"
 #include "../Graphics/Graphics.h"
 #include "../Graphics/Renderer.h"
 #include "../Graphics/Renderer.h"

+ 218 - 0
Source/Atomic/UI/SystemUI/BorderImage.cpp

@@ -0,0 +1,218 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Graphics/Texture2D.h"
+#include "../../Resource/ResourceCache.h"
+#include "BorderImage.h"
+
+#include "../../DebugNew.h"
+
+namespace Atomic
+{
+
+extern const char* blendModeNames[];
+
+
+namespace SystemUI
+{
+
+extern const char* UI_CATEGORY;
+
+BorderImage::BorderImage(Context* context) :
+    UIElement(context),
+    imageRect_(IntRect::ZERO),
+    border_(IntRect::ZERO),
+    imageBorder_(IntRect::ZERO),
+    hoverOffset_(IntVector2::ZERO),
+    blendMode_(BLEND_REPLACE),
+    tiled_(false)
+{
+}
+
+BorderImage::~BorderImage()
+{
+}
+
+void BorderImage::RegisterObject(Context* context)
+{
+    context->RegisterFactory<BorderImage>(UI_CATEGORY);
+
+    COPY_BASE_ATTRIBUTES(UIElement);
+    MIXED_ACCESSOR_ATTRIBUTE("Texture", GetTextureAttr, SetTextureAttr, ResourceRef, ResourceRef(Texture2D::GetTypeStatic()),
+        AM_FILE);
+    ACCESSOR_ATTRIBUTE("Image Rect", GetImageRect, SetImageRect, IntRect, IntRect::ZERO, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Border", GetBorder, SetBorder, IntRect, IntRect::ZERO, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Image Border", GetImageBorder, SetImageBorder, IntRect, IntRect::ZERO, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Hover Image Offset", GetHoverOffset, SetHoverOffset, IntVector2, IntVector2::ZERO, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Tiled", IsTiled, SetTiled, bool, false, AM_FILE);
+    ENUM_ACCESSOR_ATTRIBUTE("Blend Mode", GetBlendMode, SetBlendMode, BlendMode, blendModeNames, 0, AM_FILE);
+}
+
+void BorderImage::GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor)
+{
+    GetBatches(batches, vertexData, currentScissor, hovering_ || selected_ || HasFocus() ? hoverOffset_ : IntVector2::ZERO);
+}
+
+void BorderImage::SetTexture(Texture* texture)
+{
+    texture_ = texture;
+    if (imageRect_ == IntRect::ZERO)
+        SetFullImageRect();
+}
+
+void BorderImage::SetImageRect(const IntRect& rect)
+{
+    if (rect != IntRect::ZERO)
+        imageRect_ = rect;
+}
+
+void BorderImage::SetFullImageRect()
+{
+    if (texture_)
+        SetImageRect(IntRect(0, 0, texture_->GetWidth(), texture_->GetHeight()));
+}
+
+void BorderImage::SetBorder(const IntRect& rect)
+{
+    border_.left_ = Max(rect.left_, 0);
+    border_.top_ = Max(rect.top_, 0);
+    border_.right_ = Max(rect.right_, 0);
+    border_.bottom_ = Max(rect.bottom_, 0);
+}
+
+void BorderImage::SetImageBorder(const IntRect& rect)
+{
+    imageBorder_.left_ = Max(rect.left_, 0);
+    imageBorder_.top_ = Max(rect.top_, 0);
+    imageBorder_.right_ = Max(rect.right_, 0);
+    imageBorder_.bottom_ = Max(rect.bottom_, 0);
+}
+
+void BorderImage::SetHoverOffset(const IntVector2& offset)
+{
+    hoverOffset_ = offset;
+}
+
+void BorderImage::SetHoverOffset(int x, int y)
+{
+    hoverOffset_ = IntVector2(x, y);
+}
+
+void BorderImage::SetBlendMode(BlendMode mode)
+{
+    blendMode_ = mode;
+}
+
+void BorderImage::SetTiled(bool enable)
+{
+    tiled_ = enable;
+}
+
+void BorderImage::GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor,
+    const IntVector2& offset)
+{
+    bool allOpaque = true;
+    if (GetDerivedOpacity() < 1.0f || color_[C_TOPLEFT].a_ < 1.0f || color_[C_TOPRIGHT].a_ < 1.0f ||
+        color_[C_BOTTOMLEFT].a_ < 1.0f || color_[C_BOTTOMRIGHT].a_ < 1.0f)
+        allOpaque = false;
+
+    SystemUIBatch
+        batch(this, blendMode_ == BLEND_REPLACE && !allOpaque ? BLEND_ALPHA : blendMode_, currentScissor, texture_, &vertexData);
+
+    // Calculate size of the inner rect, and texture dimensions of the inner rect
+    const IntRect& uvBorder = (imageBorder_ == IntRect::ZERO) ? border_ : imageBorder_;
+    int x = GetIndentWidth();
+    IntVector2 size = GetSize();
+    size.x_ -= x;
+    IntVector2 innerSize(
+        Max(size.x_ - border_.left_ - border_.right_, 0),
+        Max(size.y_ - border_.top_ - border_.bottom_, 0));
+    IntVector2 innerUvSize(
+        Max(imageRect_.right_ - imageRect_.left_ - uvBorder.left_ - uvBorder.right_, 0),
+        Max(imageRect_.bottom_ - imageRect_.top_ - uvBorder.top_ - uvBorder.bottom_, 0));
+
+    IntVector2 uvTopLeft(imageRect_.left_, imageRect_.top_);
+    uvTopLeft += offset;
+
+    // Top
+    if (border_.top_)
+    {
+        if (border_.left_)
+            batch.AddQuad(x, 0, border_.left_, border_.top_, uvTopLeft.x_, uvTopLeft.y_, uvBorder.left_, uvBorder.top_);
+        if (innerSize.x_)
+            batch.AddQuad(x + border_.left_, 0, innerSize.x_, border_.top_, uvTopLeft.x_ + uvBorder.left_, uvTopLeft.y_,
+                innerUvSize.x_, uvBorder.top_, tiled_);
+        if (border_.right_)
+            batch.AddQuad(x + border_.left_ + innerSize.x_, 0, border_.right_, border_.top_,
+                uvTopLeft.x_ + uvBorder.left_ + innerUvSize.x_, uvTopLeft.y_, uvBorder.right_, uvBorder.top_);
+    }
+    // Middle
+    if (innerSize.y_)
+    {
+        if (border_.left_)
+            batch.AddQuad(x, border_.top_, border_.left_, innerSize.y_, uvTopLeft.x_, uvTopLeft.y_ + uvBorder.top_,
+                uvBorder.left_, innerUvSize.y_, tiled_);
+        if (innerSize.x_)
+            batch.AddQuad(x + border_.left_, border_.top_, innerSize.x_, innerSize.y_, uvTopLeft.x_ + uvBorder.left_,
+                uvTopLeft.y_ + uvBorder.top_, innerUvSize.x_, innerUvSize.y_, tiled_);
+        if (border_.right_)
+            batch.AddQuad(x + border_.left_ + innerSize.x_, border_.top_, border_.right_, innerSize.y_,
+                uvTopLeft.x_ + uvBorder.left_ + innerUvSize.x_, uvTopLeft.y_ + uvBorder.top_, uvBorder.right_, innerUvSize.y_,
+                tiled_);
+    }
+    // Bottom
+    if (border_.bottom_)
+    {
+        if (border_.left_)
+            batch.AddQuad(x, border_.top_ + innerSize.y_, border_.left_, border_.bottom_, uvTopLeft.x_,
+                uvTopLeft.y_ + uvBorder.top_ + innerUvSize.y_, uvBorder.left_, uvBorder.bottom_);
+        if (innerSize.x_)
+            batch.AddQuad(x + border_.left_, border_.top_ + innerSize.y_, innerSize.x_, border_.bottom_,
+                uvTopLeft.x_ + uvBorder.left_, uvTopLeft.y_ + uvBorder.top_ + innerUvSize.y_, innerUvSize.x_, uvBorder.bottom_,
+                tiled_);
+        if (border_.right_)
+            batch.AddQuad(x + border_.left_ + innerSize.x_, border_.top_ + innerSize.y_, border_.right_, border_.bottom_,
+                uvTopLeft.x_ + uvBorder.left_ + innerUvSize.x_, uvTopLeft.y_ + uvBorder.top_ + innerUvSize.y_, uvBorder.right_,
+                uvBorder.bottom_);
+    }
+
+    SystemUIBatch::AddOrMerge(batch, batches);
+
+    // Reset hovering for next frame
+    hovering_ = false;
+}
+
+void BorderImage::SetTextureAttr(const ResourceRef& value)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    SetTexture(cache->GetResource<Texture2D>(value.name_));
+}
+
+ResourceRef BorderImage::GetTextureAttr() const
+{
+    return GetResourceRef(texture_, Texture2D::GetTypeStatic());
+}
+
+}
+
+}

+ 121 - 0
Source/Atomic/UI/SystemUI/BorderImage.h

@@ -0,0 +1,121 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "../../Graphics/GraphicsDefs.h"
+#include "UIElement.h"
+
+namespace Atomic
+{
+
+class Texture;
+class Texture2D;
+
+namespace SystemUI
+{
+
+/// %Image %UI element with optional border.
+class ATOMIC_API BorderImage : public UIElement
+{
+    OBJECT(BorderImage);
+
+public:
+    /// Construct.
+    BorderImage(Context* context);
+    /// Destruct.
+    virtual ~BorderImage();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Return UI rendering batches.
+    virtual void GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor);
+
+    /// Set texture.
+    void SetTexture(Texture* texture);
+    /// Set part of texture to use as the image.
+    void SetImageRect(const IntRect& rect);
+    /// Use whole texture as the image.
+    void SetFullImageRect();
+    /// Set border dimensions on the screen.
+    void SetBorder(const IntRect& rect);
+    /// Set border dimensions on the image. If zero (default) uses the screen dimensions, resulting in pixel-perfect borders.
+    void SetImageBorder(const IntRect& rect);
+    /// Set offset to image rectangle used on hover.
+    void SetHoverOffset(const IntVector2& offset);
+    /// Set offset to image rectangle used on hover.
+    void SetHoverOffset(int x, int y);
+    /// Set blend mode.
+    void SetBlendMode(BlendMode mode);
+    /// Set tiled mode.
+    void SetTiled(bool enable);
+
+    /// Return texture.
+    Texture* GetTexture() const { return texture_; }
+
+    /// Return image rectangle.
+    const IntRect& GetImageRect() const { return imageRect_; }
+
+    /// Return border screen dimensions.
+    const IntRect& GetBorder() const { return border_; }
+
+    /// Return border image dimensions. Zero rect uses border screen dimensions.
+    const IntRect& GetImageBorder() const { return imageBorder_; }
+
+    /// Return offset to image rectangle used on hover.
+    const IntVector2& GetHoverOffset() const { return hoverOffset_; }
+
+    /// Return blend mode.
+    BlendMode GetBlendMode() const { return blendMode_; }
+
+    /// Return whether is tiled.
+    bool IsTiled() const { return tiled_; }
+
+    /// Set texture attribute.
+    void SetTextureAttr(const ResourceRef& value);
+    /// Return texture attribute.
+    ResourceRef GetTextureAttr() const;
+
+protected:
+    /// Return UI rendering batches with offset to image rectangle.
+    void GetBatches
+        (PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor, const IntVector2& offset);
+
+    /// Texture.
+    SharedPtr<Texture> texture_;
+    /// Image rectangle.
+    IntRect imageRect_;
+    /// Border dimensions on screen.
+    IntRect border_;
+    /// Border dimensions on the image.
+    IntRect imageBorder_;
+    /// Offset to image rectangle on hover.
+    IntVector2 hoverOffset_;
+    /// Blend mode flag.
+    BlendMode blendMode_;
+    /// Tiled flag.
+    bool tiled_;
+};
+
+}
+
+}

+ 195 - 0
Source/Atomic/UI/SystemUI/Button.cpp

@@ -0,0 +1,195 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Input/InputEvents.h"
+#include "Button.h"
+#include "SystemUI.h"
+#include "SystemUIEvents.h"
+
+#include "../../DebugNew.h"
+
+namespace Atomic
+{
+
+
+namespace SystemUI
+{
+
+extern const char* UI_CATEGORY;
+
+Button::Button(Context* context) :
+    BorderImage(context),
+    pressedOffset_(IntVector2::ZERO),
+    pressedChildOffset_(IntVector2::ZERO),
+    repeatDelay_(1.0f),
+    repeatRate_(0.0f),
+    repeatTimer_(0.0f),
+    pressed_(false)
+{
+    SetEnabled(true);
+    focusMode_ = FM_FOCUSABLE;
+}
+
+Button::~Button()
+{
+}
+
+void Button::RegisterObject(Context* context)
+{
+    context->RegisterFactory<Button>(UI_CATEGORY);
+
+    COPY_BASE_ATTRIBUTES(BorderImage);
+    UPDATE_ATTRIBUTE_DEFAULT_VALUE("Is Enabled", true);
+    UPDATE_ATTRIBUTE_DEFAULT_VALUE("Focus Mode", FM_FOCUSABLE);
+    ACCESSOR_ATTRIBUTE("Pressed Image Offset", GetPressedOffset, SetPressedOffset, IntVector2, IntVector2::ZERO, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Pressed Child Offset", GetPressedChildOffset, SetPressedChildOffset, IntVector2, IntVector2::ZERO, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Repeat Delay", GetRepeatDelay, SetRepeatDelay, float, 1.0f, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Repeat Rate", GetRepeatRate, SetRepeatRate, float, 0.0f, AM_FILE);
+}
+
+void Button::Update(float timeStep)
+{
+    if (!hovering_ && pressed_)
+        SetPressed(false);
+
+    // Send repeat events if pressed
+    if (pressed_ && repeatRate_ > 0.0f)
+    {
+        repeatTimer_ -= timeStep;
+        if (repeatTimer_ <= 0.0f)
+        {
+            repeatTimer_ += 1.0f / repeatRate_;
+
+            using namespace Pressed;
+
+            VariantMap& eventData = GetEventDataMap();
+            eventData[P_ELEMENT] = this;
+            SendEvent(E_PRESSED, eventData);
+        }
+    }
+}
+
+void Button::GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor)
+{
+    IntVector2 offset(IntVector2::ZERO);
+    if (hovering_ || HasFocus())
+        offset += hoverOffset_;
+    if (pressed_ || selected_)
+        offset += pressedOffset_;
+
+    BorderImage::GetBatches(batches, vertexData, currentScissor, offset);
+}
+
+void Button::OnClickBegin(const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers,
+    Cursor* cursor)
+{
+    if (button == MOUSEB_LEFT)
+    {
+        SetPressed(true);
+        repeatTimer_ = repeatDelay_;
+        hovering_ = true;
+
+        using namespace Pressed;
+
+        VariantMap& eventData = GetEventDataMap();
+        eventData[P_ELEMENT] = this;
+        SendEvent(E_PRESSED, eventData);
+    }
+}
+
+void Button::OnClickEnd(const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers,
+    Cursor* cursor, UIElement* beginElement)
+{
+    if (pressed_ && button == MOUSEB_LEFT)
+    {
+        SetPressed(false);
+
+        using namespace Released;
+
+        VariantMap& eventData = GetEventDataMap();
+        eventData[P_ELEMENT] = this;
+        SendEvent(E_RELEASED, eventData);
+    }
+}
+
+void Button::OnDragMove(const IntVector2& position, const IntVector2& screenPosition, const IntVector2& deltaPos, int buttons,
+    int qualifiers, Cursor* cursor)
+{
+    SetPressed(true);
+}
+
+void Button::OnKey(int key, int buttons, int qualifiers)
+{
+    if (HasFocus() && (key == KEY_RETURN || key == KEY_RETURN2 || key == KEY_KP_ENTER || key == KEY_SPACE))
+    {
+        // Simulate LMB click
+        OnClickBegin(IntVector2(), IntVector2(), MOUSEB_LEFT, 0, 0, 0);
+        OnClickEnd(IntVector2(), IntVector2(), MOUSEB_LEFT, 0, 0, 0, 0);
+    }
+}
+
+void Button::SetPressedOffset(const IntVector2& offset)
+{
+    pressedOffset_ = offset;
+}
+
+void Button::SetPressedOffset(int x, int y)
+{
+    pressedOffset_ = IntVector2(x, y);
+}
+
+void Button::SetPressedChildOffset(const IntVector2& offset)
+{
+    pressedChildOffset_ = offset;
+}
+
+void Button::SetPressedChildOffset(int x, int y)
+{
+    pressedChildOffset_ = IntVector2(x, y);
+}
+
+void Button::SetRepeat(float delay, float rate)
+{
+    SetRepeatDelay(delay);
+    SetRepeatRate(rate);
+}
+
+void Button::SetRepeatDelay(float delay)
+{
+    repeatDelay_ = Max(delay, 0.0f);
+}
+
+void Button::SetRepeatRate(float rate)
+{
+    repeatRate_ = Max(rate, 0.0f);
+}
+
+void Button::SetPressed(bool enable)
+{
+    pressed_ = enable;
+    SetChildOffset(pressed_ ? pressedChildOffset_ : IntVector2::ZERO);
+}
+
+}
+
+}

+ 114 - 0
Source/Atomic/UI/SystemUI/Button.h

@@ -0,0 +1,114 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "BorderImage.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+/// Push button %UI element.
+class ATOMIC_API Button : public BorderImage
+{
+    OBJECT(Button);
+
+public:
+    /// Construct.
+    Button(Context* context);
+    /// Destruct.
+    virtual ~Button();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Perform UI element update.
+    virtual void Update(float timeStep);
+    /// Return UI rendering batches.
+    virtual void GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor);
+    /// React to mouse click begin.
+    virtual void OnClickBegin
+        (const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers, Cursor* cursor);
+    /// React to mouse click end.
+    virtual void OnClickEnd
+        (const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers, Cursor* cursor,
+            UIElement* beginElement);
+    /// React to mouse drag motion.
+    virtual void OnDragMove
+        (const IntVector2& position, const IntVector2& screenPosition, const IntVector2& deltaPos, int buttons, int qualifiers,
+            Cursor* cursor);
+    /// React to a key press.
+    virtual void OnKey(int key, int buttons, int qualifiers);
+
+    /// Set offset to image rectangle used when pressed.
+    void SetPressedOffset(const IntVector2& offset);
+    /// Set offset to image rectangle used when pressed.
+    void SetPressedOffset(int x, int y);
+    /// Set offset of child elements when pressed.
+    void SetPressedChildOffset(const IntVector2& offset);
+    /// Set offset of child elements when pressed.
+    void SetPressedChildOffset(int x, int y);
+    /// Set repeat properties. Rate 0 (default) disables repeat.
+    void SetRepeat(float delay, float rate);
+    /// Set repeat delay.
+    void SetRepeatDelay(float delay);
+    /// Set repeat rate.
+    void SetRepeatRate(float rate);
+
+    /// Return pressed image offset.
+    const IntVector2& GetPressedOffset() const { return pressedOffset_; }
+
+    /// Return offset of child elements when pressed.
+    const IntVector2& GetPressedChildOffset() const { return pressedChildOffset_; }
+
+    /// Return repeat delay.
+    float GetRepeatDelay() const { return repeatDelay_; }
+
+    /// Return repeat rate.
+    float GetRepeatRate() const { return repeatRate_; }
+
+    /// Return whether is currently pressed.
+    bool IsPressed() const { return pressed_; }
+
+protected:
+    /// Set new pressed state.
+    void SetPressed(bool enable);
+
+    /// Pressed image offset.
+    IntVector2 pressedOffset_;
+    /// Pressed label offset.
+    IntVector2 pressedChildOffset_;
+    /// Repeat delay.
+    float repeatDelay_;
+    /// Repeat rate.
+    float repeatRate_;
+    /// Repeat timer.
+    float repeatTimer_;
+    /// Current pressed state.
+    bool pressed_;
+};
+
+}
+
+}

+ 117 - 0
Source/Atomic/UI/SystemUI/CheckBox.cpp

@@ -0,0 +1,117 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Input/InputEvents.h"
+#include "CheckBox.h"
+#include "SystemUIEvents.h"
+
+#include "../../DebugNew.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+extern const char* UI_CATEGORY;
+
+CheckBox::CheckBox(Context* context) :
+    BorderImage(context),
+    checkedOffset_(IntVector2::ZERO),
+    checked_(false)
+{
+    SetEnabled(true);
+    focusMode_ = FM_FOCUSABLE_DEFOCUSABLE;
+}
+
+CheckBox::~CheckBox()
+{
+}
+
+void CheckBox::RegisterObject(Context* context)
+{
+    context->RegisterFactory<CheckBox>(UI_CATEGORY);
+
+    COPY_BASE_ATTRIBUTES(BorderImage);
+    UPDATE_ATTRIBUTE_DEFAULT_VALUE("Is Enabled", true);
+    UPDATE_ATTRIBUTE_DEFAULT_VALUE("Focus Mode", FM_FOCUSABLE_DEFOCUSABLE);
+    ACCESSOR_ATTRIBUTE("Is Checked", IsChecked, SetChecked, bool, false, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Checked Image Offset", GetCheckedOffset, SetCheckedOffset, IntVector2, IntVector2::ZERO, AM_FILE);
+}
+
+void CheckBox::GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor)
+{
+    IntVector2 offset(IntVector2::ZERO);
+    if (hovering_ || selected_ || HasFocus())
+        offset += hoverOffset_;
+    if (checked_)
+        offset += checkedOffset_;
+
+    BorderImage::GetBatches(batches, vertexData, currentScissor, offset);
+}
+
+void CheckBox::OnClickBegin(const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers,
+    Cursor* cursor)
+{
+    if (button == MOUSEB_LEFT && editable_)
+        SetChecked(!checked_);
+}
+
+void CheckBox::OnKey(int key, int buttons, int qualifiers)
+{
+    if (HasFocus() && key == KEY_SPACE)
+    {
+        // Simulate LMB click
+        OnClickBegin(IntVector2(), IntVector2(), MOUSEB_LEFT, 0, 0, 0);
+    }
+}
+
+void CheckBox::SetChecked(bool enable)
+{
+    if (enable != checked_)
+    {
+        checked_ = enable;
+
+        using namespace Toggled;
+
+        VariantMap& eventData = GetEventDataMap();
+        eventData[P_ELEMENT] = this;
+        eventData[P_STATE] = checked_;
+        SendEvent(E_TOGGLED, eventData);
+    }
+}
+
+void CheckBox::SetCheckedOffset(const IntVector2& offset)
+{
+    checkedOffset_ = offset;
+}
+
+void CheckBox::SetCheckedOffset(int x, int y)
+{
+    checkedOffset_ = IntVector2(x, y);
+}
+
+}
+
+}

+ 77 - 0
Source/Atomic/UI/SystemUI/CheckBox.h

@@ -0,0 +1,77 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "BorderImage.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+
+/// %UI element that can be toggled between unchecked and checked state.
+class ATOMIC_API CheckBox : public BorderImage
+{
+    OBJECT(CheckBox);
+
+public:
+    /// Construct.
+    CheckBox(Context* context);
+    /// Destruct.
+    virtual ~CheckBox();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Return UI rendering batches.
+    virtual void GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor);
+    /// React to mouse click begin.
+    virtual void OnClickBegin
+        (const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers, Cursor* cursor);
+    /// React to a key press.
+    virtual void OnKey(int key, int buttons, int qualifiers);
+
+    /// Set checked state.
+    void SetChecked(bool enable);
+    /// Set checked image offset.
+    void SetCheckedOffset(const IntVector2& rect);
+    /// Set checked image offset.
+    void SetCheckedOffset(int x, int y);
+
+    /// Return whether is checked.
+    bool IsChecked() const { return checked_; }
+
+    /// Return checked image offset.
+    const IntVector2& GetCheckedOffset() const { return checkedOffset_; }
+
+protected:
+    /// Checked image offset.
+    IntVector2 checkedOffset_;
+    /// Current checked state.
+    bool checked_;
+};
+
+}
+
+}

+ 28 - 28
Source/Atomic/Engine/Console.cpp → Source/Atomic/UI/SystemUI/Console.cpp

@@ -1,5 +1,3 @@
-#ifdef __DISABLED
-
 //
 //
 // Copyright (c) 2008-2015 the Urho3D project.
 // Copyright (c) 2008-2015 the Urho3D project.
 //
 //
@@ -22,32 +20,33 @@
 // THE SOFTWARE.
 // THE SOFTWARE.
 //
 //
 
 
-#include "../Precompiled.h"
-
-#include "../Core/Context.h"
-#include "../Core/CoreEvents.h"
-#include "../Engine/Console.h"
-#include "../Engine/EngineEvents.h"
-#include "../Graphics/Graphics.h"
-#include "../Graphics/GraphicsEvents.h"
-#include "../Input/Input.h"
-#include "../IO/IOEvents.h"
-#include "../IO/Log.h"
-#include "../Resource/ResourceCache.h"
-#include "../UI/DropDownList.h"
-#include "../UI/Font.h"
-#include "../UI/LineEdit.h"
-#include "../UI/ListView.h"
-#include "../UI/ScrollBar.h"
-#include "../UI/Text.h"
-#include "../UI/UI.h"
-#include "../UI/UIEvents.h"
-
-#include "../DebugNew.h"
+#include "../../Core/Context.h"
+#include "../../Core/CoreEvents.h"
+#include "../../Engine/EngineEvents.h"
+#include "../../Graphics/Graphics.h"
+#include "../../Graphics/GraphicsEvents.h"
+#include "../../Input/Input.h"
+#include "../../IO/IOEvents.h"
+#include "../../IO/Log.h"
+#include "../../Resource/ResourceCache.h"
+#include "DropDownList.h"
+#include "Font.h"
+#include "LineEdit.h"
+#include "ListView.h"
+#include "ScrollBar.h"
+#include "Text.h"
+#include "SystemUI.h"
+#include "SystemUIEvents.h"
+#include "Console.h"
+
+#include "../../DebugNew.h"
 
 
 namespace Atomic
 namespace Atomic
 {
 {
 
 
+namespace SystemUI
+{
+
 static const int DEFAULT_CONSOLE_ROWS = 16;
 static const int DEFAULT_CONSOLE_ROWS = 16;
 static const int DEFAULT_HISTORY_SIZE = 16;
 static const int DEFAULT_HISTORY_SIZE = 16;
 
 
@@ -58,7 +57,7 @@ Console::Console(Context* context) :
     historyPosition_(0),
     historyPosition_(0),
     printing_(false)
     printing_(false)
 {
 {
-    UI* ui = GetSubsystem<UI>();
+    SystemUI* ui = GetSubsystem<SystemUI>();
     UIElement* uiRoot = ui->GetRoot();
     UIElement* uiRoot = ui->GetRoot();
 
 
     // By default prevent the automatic showing of the screen keyboard
     // By default prevent the automatic showing of the screen keyboard
@@ -138,7 +137,7 @@ void Console::SetVisible(bool enable)
         bool hasInterpreter = PopulateInterpreter();
         bool hasInterpreter = PopulateInterpreter();
         commandLine_->SetVisible(hasInterpreter);
         commandLine_->SetVisible(hasInterpreter);
         if (hasInterpreter && focusOnShow_)
         if (hasInterpreter && focusOnShow_)
-            GetSubsystem<UI>()->SetFocusElement(lineEdit_);
+            GetSubsystem<SystemUI>()->SetFocusElement(lineEdit_);
 
 
         // Ensure the background has no empty space when shown without the lineedit
         // Ensure the background has no empty space when shown without the lineedit
         background_->SetHeight(background_->GetMinHeight());
         background_->SetHeight(background_->GetMinHeight());
@@ -378,6 +377,7 @@ void Console::HandleLineEditKey(StringHash eventType, VariantMap& eventData)
 void Console::HandleCloseButtonPressed(StringHash eventType, VariantMap& eventData)
 void Console::HandleCloseButtonPressed(StringHash eventType, VariantMap& eventData)
 {
 {
     SetVisible(false);
     SetVisible(false);
+    SendEvent(E_CONSOLECLOSED);
 }
 }
 
 
 void Console::HandleScreenMode(StringHash eventType, VariantMap& eventData)
 void Console::HandleScreenMode(StringHash eventType, VariantMap& eventData)
@@ -409,7 +409,7 @@ void Console::HandlePostUpdate(StringHash eventType, VariantMap& eventData)
     // Ensure UI-elements are not detached
     // Ensure UI-elements are not detached
     if (!background_->GetParent())
     if (!background_->GetParent())
     {
     {
-        UI* ui = GetSubsystem<UI>();
+        SystemUI* ui = GetSubsystem<SystemUI>();
         UIElement* uiRoot = ui->GetRoot();
         UIElement* uiRoot = ui->GetRoot();
         uiRoot->AddChild(background_);
         uiRoot->AddChild(background_);
         uiRoot->AddChild(closeButton_);
         uiRoot->AddChild(closeButton_);
@@ -443,4 +443,4 @@ void Console::HandlePostUpdate(StringHash eventType, VariantMap& eventData)
 
 
 }
 }
 
 
-#endif
+}

+ 176 - 0
Source/Atomic/UI/SystemUI/Console.h

@@ -0,0 +1,176 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "../../Core/Object.h"
+
+namespace Atomic
+{
+
+class Engine;
+class XMLFile;
+
+namespace SystemUI
+{
+
+class Button;
+class BorderImage;
+class DropDownList;
+class Font;
+class LineEdit;
+class ListView;
+class Text;
+class UIElement;
+
+/// %Console window with log history and command line prompt.
+class ATOMIC_API Console : public Object
+{
+    OBJECT(Console);
+
+public:
+    /// Construct.
+    Console(Context* context);
+    /// Destruct.
+    ~Console();
+
+    /// Set UI elements' style from an XML file.
+    void SetDefaultStyle(XMLFile* style);
+    /// Show or hide.
+    void SetVisible(bool enable);
+    /// Toggle visibility.
+    void Toggle();
+
+    /// Automatically set console to visible when receiving an error log message.
+    void SetAutoVisibleOnError(bool enable) { autoVisibleOnError_ = enable; }
+
+    /// Set the command interpreter.
+    void SetCommandInterpreter(const String& interpreter) { commandInterpreter_ = interpreter; }
+
+    /// Set number of buffered rows.
+    void SetNumBufferedRows(unsigned rows);
+    /// Set number of displayed rows.
+    void SetNumRows(unsigned rows);
+    /// Set command history maximum size, 0 disables history.
+    void SetNumHistoryRows(unsigned rows);
+    /// Set whether to automatically focus the line edit when showing. Default true on desktops and false on mobile devices, as on mobiles it would pop up the screen keyboard.
+    void SetFocusOnShow(bool enable);
+    /// Update elements to layout properly. Call this after manually adjusting the sub-elements.
+    void UpdateElements();
+
+    /// Return the UI style file.
+    XMLFile* GetDefaultStyle() const;
+
+    /// Return the background element.
+    BorderImage* GetBackground() const { return background_; }
+
+    /// Return the line edit element.
+    LineEdit* GetLineEdit() const { return lineEdit_; }
+
+    /// Return the close butoon element.
+    Button* GetCloseButton() const { return closeButton_; }
+
+    /// Return whether is visible.
+    bool IsVisible() const;
+
+    /// Return true when console is set to automatically visible when receiving an error log message.
+    bool IsAutoVisibleOnError() const { return autoVisibleOnError_; }
+
+    /// Return the last used command interpreter.
+    const String& GetCommandInterpreter() const { return commandInterpreter_; }
+
+    /// Return number of buffered rows.
+    unsigned GetNumBufferedRows() const;
+
+    /// Return number of displayed rows.
+    unsigned GetNumRows() const { return displayedRows_; }
+
+    /// Copy selected rows to system clipboard.
+    void CopySelectedRows() const;
+
+    /// Return history maximum size.
+    unsigned GetNumHistoryRows() const { return historyRows_; }
+
+    /// Return current history position.
+    unsigned GetHistoryPosition() const { return historyPosition_; }
+
+    /// Return history row at index.
+    const String& GetHistoryRow(unsigned index) const;
+
+    /// Return whether automatically focuses the line edit when showing.
+    bool GetFocusOnShow() const { return focusOnShow_; }
+
+private:
+    /// Populate the command line interpreters that could handle the console command.
+    bool PopulateInterpreter();
+    /// Handle interpreter being selected on the drop down list.
+    void HandleInterpreterSelected(StringHash eventType, VariantMap& eventData);
+    /// Handle enter pressed on the line edit.
+    void HandleTextFinished(StringHash eventType, VariantMap& eventData);
+    /// Handle unhandled key on the line edit for scrolling the history.
+    void HandleLineEditKey(StringHash eventType, VariantMap& eventData);
+    /// Handle close button being pressed.
+    void HandleCloseButtonPressed(StringHash eventType, VariantMap& eventData);
+    /// Handle rendering window resize.
+    void HandleScreenMode(StringHash eventType, VariantMap& eventData);
+    /// Handle a log message.
+    void HandleLogMessage(StringHash eventType, VariantMap& eventData);
+    /// Handle the application post-update.
+    void HandlePostUpdate(StringHash eventType, VariantMap& eventData);
+
+    /// Auto visible on error flag.
+    bool autoVisibleOnError_;
+    /// Background.
+    SharedPtr<BorderImage> background_;
+    /// Container for text rows.
+    ListView* rowContainer_;
+    /// Container for the command line.
+    UIElement* commandLine_;
+    /// Interpreter drop down list.
+    DropDownList* interpreters_;
+    /// Line edit.
+    LineEdit* lineEdit_;
+    /// Close button.
+    SharedPtr<Button> closeButton_;
+    /// Last used command interpreter.
+    String commandInterpreter_;
+    /// Command history.
+    Vector<String> history_;
+    /// Pending log message rows.
+    Vector<Pair<int, String> > pendingRows_;
+    /// Current row being edited.
+    String currentRow_;
+    /// Maximum displayed rows.
+    unsigned displayedRows_;
+    /// Command history maximum rows.
+    unsigned historyRows_;
+    /// Command history current position.
+    unsigned historyPosition_;
+    /// Flag when printing messages to prevent endless loop.
+    bool printing_;
+    /// Flag for automatically focusing the line edit on showing the console.
+    bool focusOnShow_;
+};
+
+}
+
+}

+ 312 - 0
Source/Atomic/UI/SystemUI/Cursor.cpp

@@ -0,0 +1,312 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Graphics/Texture2D.h"
+#include "../../Input/Input.h"
+#include "../../IO/Log.h"
+#include "../../Resource/ResourceCache.h"
+#include "SystemUI.h"
+
+#include "../../DebugNew.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+static const char* shapeNames[] =
+{
+    "Normal",
+    "IBeam",
+    "Cross",
+    "ResizeVertical",
+    "ResizeDiagonalTopRight",
+    "ResizeHorizontal",
+    "ResizeDiagonalTopLeft",
+    "ResizeAll",
+    "AcceptDrop",
+    "RejectDrop",
+    "Busy",
+    "BusyArrow"
+};
+
+/// OS cursor shape lookup table matching cursor shape enumeration
+#if !defined(ANDROID) && !defined(IOS)
+static const int osCursorLookup[CS_MAX_SHAPES] =
+{
+    SDL_SYSTEM_CURSOR_ARROW,    // CS_NORMAL
+    SDL_SYSTEM_CURSOR_IBEAM,     // CS_IBEAM
+    SDL_SYSTEM_CURSOR_CROSSHAIR, // CS_CROSS
+    SDL_SYSTEM_CURSOR_SIZENS,   // CS_RESIZEVERTICAL
+    SDL_SYSTEM_CURSOR_SIZENESW, // CS_RESIZEDIAGONAL_TOPRIGHT
+    SDL_SYSTEM_CURSOR_SIZEWE,   // CS_RESIZEHORIZONTAL
+    SDL_SYSTEM_CURSOR_SIZENWSE, // CS_RESIZEDIAGONAL_TOPLEFT
+    SDL_SYSTEM_CURSOR_SIZEALL,   // CS_RESIZE_ALL
+    SDL_SYSTEM_CURSOR_HAND,     // CS_ACCEPTDROP
+    SDL_SYSTEM_CURSOR_NO,       // CS_REJECTDROP
+    SDL_SYSTEM_CURSOR_WAIT,   // CS_BUSY
+    SDL_SYSTEM_CURSOR_WAITARROW // CS_BUSY_ARROW
+};
+#endif
+
+extern const char* UI_CATEGORY;
+
+Cursor::Cursor(Context* context) :
+    BorderImage(context),
+    shape_(shapeNames[CS_NORMAL]),
+    useSystemShapes_(false),
+    osShapeDirty_(false)
+{
+    // Define the defaults for system cursor usage.
+    for (unsigned i = 0; i < CS_MAX_SHAPES; i++)
+        shapeInfos_[shapeNames[i]] = CursorShapeInfo(i);
+
+    // Subscribe to OS mouse cursor visibility changes to be able to reapply the cursor shape
+    SubscribeToEvent(E_MOUSEVISIBLECHANGED, HANDLER(Cursor, HandleMouseVisibleChanged));
+}
+
+Cursor::~Cursor()
+{
+    for (HashMap<String, CursorShapeInfo>::Iterator i = shapeInfos_.Begin(); i != shapeInfos_.End(); ++i)
+    {
+        if (i->second_.osCursor_)
+        {
+            SDL_FreeCursor(i->second_.osCursor_);
+            i->second_.osCursor_ = 0;
+        }
+    }
+}
+
+void Cursor::RegisterObject(Context* context)
+{
+    context->RegisterFactory<Cursor>(UI_CATEGORY);
+
+    COPY_BASE_ATTRIBUTES(BorderImage);
+    UPDATE_ATTRIBUTE_DEFAULT_VALUE("Priority", M_MAX_INT);
+    ACCESSOR_ATTRIBUTE("Use System Shapes", GetUseSystemShapes, SetUseSystemShapes, bool, false, AM_FILE);
+    MIXED_ACCESSOR_ATTRIBUTE("Shapes", GetShapesAttr, SetShapesAttr, VariantVector, Variant::emptyVariantVector, AM_FILE);
+}
+
+void Cursor::GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor)
+{
+    unsigned initialSize = vertexData.Size();
+    const IntVector2& offset = shapeInfos_[shape_].hotSpot_;
+    Vector2 floatOffset(-(float)offset.x_, -(float)offset.y_);
+
+    BorderImage::GetBatches(batches, vertexData, currentScissor);
+    for (unsigned i = initialSize; i < vertexData.Size(); i += 6)
+    {
+        vertexData[i] += floatOffset.x_;
+        vertexData[i + 1] += floatOffset.y_;
+    }
+}
+
+void Cursor::DefineShape(CursorShape shape, Image* image, const IntRect& imageRect, const IntVector2& hotSpot)
+{
+    if (shape < CS_NORMAL || shape >= CS_MAX_SHAPES)
+    {
+        LOGERROR("Shape index out of bounds, can not define cursor shape");
+        return;
+    }
+
+    DefineShape(shapeNames[shape], image, imageRect, hotSpot);
+}
+
+void Cursor::DefineShape(const String& shape, Image* image, const IntRect& imageRect, const IntVector2& hotSpot)
+{
+    if (!image)
+        return;
+
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+
+    if (!shapeInfos_.Contains(shape))
+        shapeInfos_[shape] = CursorShapeInfo();
+
+    CursorShapeInfo& info = shapeInfos_[shape];
+
+    // Prefer to get the texture with same name from cache to prevent creating several copies of the texture
+    info.texture_ = cache->GetResource<Texture2D>(image->GetName(), false);
+    if (!info.texture_)
+    {
+        Texture2D* texture = new Texture2D(context_);
+        texture->SetData(SharedPtr<Image>(image));
+        info.texture_ = texture;
+    }
+
+    info.image_ = image;
+    info.imageRect_ = imageRect;
+    info.hotSpot_ = hotSpot;
+
+    // Remove existing SDL cursor
+    if (info.osCursor_)
+    {
+        SDL_FreeCursor(info.osCursor_);
+        info.osCursor_ = 0;
+    }
+
+    // Reset current shape if it was edited
+    if (shape_ == shape)
+    {
+        shape_ = String::EMPTY;
+        SetShape(shape);
+    }
+}
+
+
+void Cursor::SetShape(const String& shape)
+{
+    if (shape == String::EMPTY || shape.Empty() || shape_ == shape || !shapeInfos_.Contains(shape))
+        return;
+
+    shape_ = shape;
+
+    CursorShapeInfo& info = shapeInfos_[shape_];
+    texture_ = info.texture_;
+    imageRect_ = info.imageRect_;
+    SetSize(info.imageRect_.Size());
+
+    // To avoid flicker, the UI subsystem will apply the OS shape once per frame. Exception: if we are using the
+    // busy shape, set it immediately as we may block before that
+    osShapeDirty_ = true;
+    if (shape_ == shapeNames[CS_BUSY])
+        ApplyOSCursorShape();
+}
+
+void Cursor::SetShape(CursorShape shape)
+{
+    if (shape < CS_NORMAL || shape >= CS_MAX_SHAPES || shape_ == shapeNames[shape])
+        return;
+
+    SetShape(shapeNames[shape]);
+}
+
+void Cursor::SetUseSystemShapes(bool enable)
+{
+    if (enable != useSystemShapes_)
+    {
+        useSystemShapes_ = enable;
+        // Reapply current shape
+        osShapeDirty_ = true;
+    }
+}
+
+void Cursor::SetShapesAttr(const VariantVector& value)
+{
+    if (!value.Size())
+        return;
+
+    for (VariantVector::ConstIterator i = value.Begin(); i != value.End(); ++i)
+    {
+        VariantVector shapeVector = i->GetVariantVector();
+        if (shapeVector.Size() >= 4)
+        {
+            String shape = shapeVector[0].GetString();
+            ResourceRef ref = shapeVector[1].GetResourceRef();
+            IntRect imageRect = shapeVector[2].GetIntRect();
+            IntVector2 hotSpot = shapeVector[3].GetIntVector2();
+
+            DefineShape(shape, GetSubsystem<ResourceCache>()->GetResource<Image>(ref.name_), imageRect, hotSpot);
+        }
+    }
+}
+
+VariantVector Cursor::GetShapesAttr() const
+{
+    VariantVector ret;
+
+    for (HashMap<String, CursorShapeInfo>::ConstIterator i = shapeInfos_.Begin(); i != shapeInfos_.End(); ++i)
+    {
+        if (i->second_.imageRect_ != IntRect::ZERO)
+        {
+            // Could use a map but this simplifies the UI xml.
+            VariantVector shape;
+            shape.Push(i->first_);
+            shape.Push(GetResourceRef(i->second_.texture_, Texture2D::GetTypeStatic()));
+            shape.Push(i->second_.imageRect_);
+            shape.Push(i->second_.hotSpot_);
+            ret.Push(shape);
+        }
+    }
+
+    return ret;
+}
+
+void Cursor::ApplyOSCursorShape()
+{
+    // Mobile platforms do not support applying OS cursor shapes: comment out to avoid log error messages
+#if !defined(ANDROID) && !defined(IOS)
+    if (!osShapeDirty_ || !GetSubsystem<Input>()->IsMouseVisible() || GetSubsystem<SystemUI>()->GetCursor() != this)
+        return;
+
+    CursorShapeInfo& info = shapeInfos_[shape_];
+
+    // Remove existing SDL cursor if is not a system shape while we should be using those, or vice versa
+    if (info.osCursor_ && info.systemDefined_ != useSystemShapes_)
+    {
+        SDL_FreeCursor(info.osCursor_);
+        info.osCursor_ = 0;
+    }
+
+    // Create SDL cursor now if necessary
+    if (!info.osCursor_)
+    {
+        // Create a system default shape
+        if (useSystemShapes_ && info.systemCursor_ >= 0 && info.systemCursor_ < CS_MAX_SHAPES)
+        {
+            info.osCursor_ = SDL_CreateSystemCursor((SDL_SystemCursor)osCursorLookup[info.systemCursor_]);
+            info.systemDefined_ = true;
+            if (!info.osCursor_)
+                LOGERROR("Could not create system cursor");
+        }
+        // Create from image
+        else if (info.image_)
+        {
+            SDL_Surface* surface = info.image_->GetSDLSurface(info.imageRect_);
+
+            if (surface)
+            {
+                info.osCursor_ = SDL_CreateColorCursor(surface, info.hotSpot_.x_, info.hotSpot_.y_);
+                info.systemDefined_ = false;
+                if (!info.osCursor_)
+                    LOGERROR("Could not create cursor from image " + info.image_->GetName());
+                SDL_FreeSurface(surface);
+            }
+        }
+    }
+
+    if (info.osCursor_)
+        SDL_SetCursor(info.osCursor_);
+
+    osShapeDirty_ = false;
+#endif
+}
+
+void Cursor::HandleMouseVisibleChanged(StringHash eventType, VariantMap& eventData)
+{
+    ApplyOSCursorShape();
+}
+
+}
+
+}

+ 151 - 0
Source/Atomic/UI/SystemUI/Cursor.h

@@ -0,0 +1,151 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "../../Graphics/Texture.h"
+#include "../../Resource/Image.h"
+#include "BorderImage.h"
+
+#include <SDL/include/SDL_mouse.h>
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+
+/// %Cursor shapes recognized by the UI subsystem.
+enum CursorShape
+{
+    CS_NORMAL = 0,
+    CS_IBEAM,
+    CS_CROSS,
+    CS_RESIZEVERTICAL,
+    CS_RESIZEDIAGONAL_TOPRIGHT,
+    CS_RESIZEHORIZONTAL,
+    CS_RESIZEDIAGONAL_TOPLEFT,
+    CS_RESIZE_ALL,
+    CS_ACCEPTDROP,
+    CS_REJECTDROP,
+    CS_BUSY,
+    CS_BUSY_ARROW,
+    CS_MAX_SHAPES
+};
+
+/// %Cursor image and hotspot information.
+struct ATOMIC_API CursorShapeInfo
+{
+    /// Construct with defaults.
+    CursorShapeInfo() :
+        imageRect_(IntRect::ZERO),
+        hotSpot_(IntVector2::ZERO),
+        osCursor_(0),
+        systemDefined_(false),
+        systemCursor_(-1)
+    {
+    }
+
+    /// Construct with system cursor.
+    CursorShapeInfo(int systemCursor) :
+        imageRect_(IntRect::ZERO),
+        hotSpot_(IntVector2::ZERO),
+        osCursor_(0),
+        systemDefined_(false),
+        systemCursor_(systemCursor)
+    {
+    }
+
+    /// Image.
+    SharedPtr<Image> image_;
+    /// Texture.
+    SharedPtr<Texture> texture_;
+    /// Image rectangle.
+    IntRect imageRect_;
+    /// Hotspot coordinates.
+    IntVector2 hotSpot_;
+    /// OS cursor.
+    SDL_Cursor* osCursor_;
+    /// Whether the OS cursor is system defined.
+    bool systemDefined_;
+    /// System cursor index.
+    int systemCursor_;
+};
+
+/// Mouse cursor %UI element.
+class ATOMIC_API Cursor : public BorderImage
+{
+    OBJECT(Cursor);
+
+public:
+    /// Construct.
+    Cursor(Context* context);
+    /// Destruct.
+    virtual ~Cursor();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Return UI rendering batches.
+    virtual void GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor);
+
+    /// Define a shape.
+    void DefineShape(const String& shape, Image* image, const IntRect& imageRect, const IntVector2& hotSpot);
+    /// Define a shape.
+    void DefineShape(CursorShape shape, Image* image, const IntRect& imageRect, const IntVector2& hotSpot);
+    /// Set current shape.
+    void SetShape(const String& shape);
+    /// Set current shape.
+    void SetShape(CursorShape shape);
+    /// Set whether to use system default shapes. Is only possible when the OS mouse cursor has been set visible from the Input subsystem.
+    void SetUseSystemShapes(bool enable);
+
+    /// Get current shape.
+    const String& GetShape() const { return shape_; }
+
+    /// Return whether is using system default shapes.
+    bool GetUseSystemShapes() const { return useSystemShapes_; }
+
+    /// Set shapes attribute.
+    void SetShapesAttr(const VariantVector& value);
+    /// Return shapes attribute.
+    VariantVector GetShapesAttr() const;
+    /// Apply pending OS cursor shape. Called by UI. No-op when the OS mouse pointer is not used.
+    void ApplyOSCursorShape();
+
+protected:
+    /// Handle operating system mouse cursor visibility change event.
+    void HandleMouseVisibleChanged(StringHash eventType, VariantMap& eventData);
+
+    /// Current shape definition.
+    String shape_;
+    /// Shape definitions.
+    HashMap<String, CursorShapeInfo> shapeInfos_;
+    /// Use system default shapes flag.
+    bool useSystemShapes_;
+    /// OS cursor shape needs update flag.
+    bool osShapeDirty_;
+};
+
+}
+
+}

+ 17 - 16
Source/Atomic/Engine/DebugHud.cpp → Source/Atomic/UI/SystemUI/DebugHud.cpp

@@ -1,5 +1,3 @@
-#ifdef __DISABLED
-
 //
 //
 // Copyright (c) 2008-2015 the Urho3D project.
 // Copyright (c) 2008-2015 the Urho3D project.
 //
 //
@@ -22,21 +20,25 @@
 // THE SOFTWARE.
 // THE SOFTWARE.
 //
 //
 
 
-#include "../Precompiled.h"
-
-#include "../Core/CoreEvents.h"
-#include "../Core/Profiler.h"
-#include "../Engine/DebugHud.h"
-#include "../Engine/Engine.h"
-#include "../Graphics/Graphics.h"
-#include "../Graphics/Renderer.h"
-#include "../IO/Log.h"
+#include "../../Core/CoreEvents.h"
+#include "../../Core/Profiler.h"
+#include "../../Engine/Engine.h"
+#include "../../Graphics/Graphics.h"
+#include "../../Graphics/Renderer.h"
+#include "../../IO/Log.h"
+#include "Font.h"
+#include "Text.h"
+#include "SystemUI.h"
+#include "DebugHud.h"
 
 
-#include "../DebugNew.h"
+#include "../../DebugNew.h"
 
 
 namespace Atomic
 namespace Atomic
 {
 {
 
 
+namespace SystemUI
+{
+
 static const char* qualityTexts[] =
 static const char* qualityTexts[] =
 {
 {
     "Low",
     "Low",
@@ -59,7 +61,7 @@ DebugHud::DebugHud(Context* context) :
     useRendererStats_(false),
     useRendererStats_(false),
     mode_(DEBUGHUD_SHOW_NONE)
     mode_(DEBUGHUD_SHOW_NONE)
 {
 {
-    UI* ui = GetSubsystem<UI>();
+    SystemUI* ui = GetSubsystem<SystemUI>();
     UIElement* uiRoot = ui->GetRoot();
     UIElement* uiRoot = ui->GetRoot();
 
 
     statsText_ = new Text(context_);
     statsText_ = new Text(context_);
@@ -100,7 +102,7 @@ void DebugHud::Update()
     // Ensure UI-elements are not detached
     // Ensure UI-elements are not detached
     if (!statsText_->GetParent())
     if (!statsText_->GetParent())
     {
     {
-        UI* ui = GetSubsystem<UI>();
+        SystemUI* ui = GetSubsystem<SystemUI>();
         UIElement* uiRoot = ui->GetRoot();
         UIElement* uiRoot = ui->GetRoot();
         uiRoot->AddChild(statsText_);
         uiRoot->AddChild(statsText_);
         uiRoot->AddChild(modeText_);
         uiRoot->AddChild(modeText_);
@@ -264,5 +266,4 @@ void DebugHud::HandlePostUpdate(StringHash eventType, VariantMap& eventData)
 
 
 }
 }
 
 
-
-#endif
+}

+ 133 - 0
Source/Atomic/UI/SystemUI/DebugHud.h

@@ -0,0 +1,133 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "../../Core/Object.h"
+#include "../../Core/Timer.h"
+
+namespace Atomic
+{
+
+class Engine;
+class XMLFile;
+
+namespace SystemUI
+{
+
+class Font;
+class Text;
+
+static const unsigned DEBUGHUD_SHOW_NONE = 0x0;
+static const unsigned DEBUGHUD_SHOW_STATS = 0x1;
+static const unsigned DEBUGHUD_SHOW_MODE = 0x2;
+static const unsigned DEBUGHUD_SHOW_PROFILER = 0x4;
+static const unsigned DEBUGHUD_SHOW_ALL = 0x7;
+
+/// Displays rendering stats and profiling information.
+class ATOMIC_API DebugHud : public Object
+{
+    OBJECT(DebugHud);
+
+public:
+    /// Construct.
+    DebugHud(Context* context);
+    /// Destruct.
+    ~DebugHud();
+
+    /// Update. Called by HandlePostUpdate().
+    void Update();
+    /// Set UI elements' style from an XML file.
+    void SetDefaultStyle(XMLFile* style);
+    /// Set elements to show.
+    void SetMode(unsigned mode);
+    /// Set maximum profiler block depth, default unlimited.
+    void SetProfilerMaxDepth(unsigned depth);
+    /// Set profiler accumulation interval in seconds.
+    void SetProfilerInterval(float interval);
+    /// Set whether to show 3D geometry primitive/batch count only. Default false.
+    void SetUseRendererStats(bool enable);
+    /// Toggle elements.
+    void Toggle(unsigned mode);
+    /// Toggle all elements.
+    void ToggleAll();
+
+    /// Return the UI style file.
+    XMLFile* GetDefaultStyle() const;
+
+    /// Return rendering stats text.
+    Text* GetStatsText() const { return statsText_; }
+
+    /// Return rendering mode text.
+    Text* GetModeText() const { return modeText_; }
+
+    /// Return profiler text.
+    Text* GetProfilerText() const { return profilerText_; }
+
+    /// Return currently shown elements.
+    unsigned GetMode() const { return mode_; }
+
+    /// Return maximum profiler block depth.
+    unsigned GetProfilerMaxDepth() const { return profilerMaxDepth_; }
+
+    /// Return profiler accumulation interval in seconds
+    float GetProfilerInterval() const;
+
+    /// Return whether showing 3D geometry primitive/batch count only.
+    bool GetUseRendererStats() const { return useRendererStats_; }
+
+    /// Set application-specific stats.
+    void SetAppStats(const String& label, const Variant& stats);
+    /// Set application-specific stats.
+    void SetAppStats(const String& label, const String& stats);
+    /// Reset application-specific stats. Return true if it was erased successfully.
+    bool ResetAppStats(const String& label);
+    /// Clear all application-specific stats.
+    void ClearAppStats();
+
+private:
+    /// Handle logic post-update event. The HUD texts are updated here.
+    void HandlePostUpdate(StringHash eventType, VariantMap& eventData);
+
+    /// Rendering stats text.
+    SharedPtr<Text> statsText_;
+    /// Rendering mode text.
+    SharedPtr<Text> modeText_;
+    /// Profiling information text.
+    SharedPtr<Text> profilerText_;
+    /// Hashmap containing application specific stats.
+    HashMap<String, String> appStats_;
+    /// Profiler timer.
+    Timer profilerTimer_;
+    /// Profiler max block depth.
+    unsigned profilerMaxDepth_;
+    /// Profiler accumulation interval.
+    unsigned profilerInterval_;
+    /// Show 3D geometry primitive/batch count flag.
+    bool useRendererStats_;
+    /// Current shown-element mode.
+    unsigned mode_;
+};
+
+}
+
+}

+ 346 - 0
Source/Atomic/UI/SystemUI/DropDownList.cpp

@@ -0,0 +1,346 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Input/InputEvents.h"
+#include "../../IO/Log.h"
+#include "DropDownList.h"
+#include "ListView.h"
+#include "Text.h"
+#include "SystemUI.h"
+#include "SystemUIEvents.h"
+#include "Window.h"
+
+#include "../../DebugNew.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+extern const char* UI_CATEGORY;
+
+DropDownList::DropDownList(Context* context) :
+    Menu(context),
+    resizePopup_(false),
+    selectionAttr_(0)
+{
+    focusMode_ = FM_FOCUSABLE_DEFOCUSABLE;
+
+    Window* window = new Window(context_);
+    window->SetInternal(true);
+    SetPopup(window);
+
+    listView_ = new ListView(context_);
+    listView_->SetInternal(true);
+    listView_->SetScrollBarsVisible(false, false);
+    popup_->SetLayout(LM_VERTICAL);
+    popup_->AddChild(listView_);
+    placeholder_ = CreateChild<UIElement>("DDL_Placeholder");
+    placeholder_->SetInternal(true);
+    Text* text = placeholder_->CreateChild<Text>("DDL_Placeholder_Text");
+    text->SetInternal(true);
+    text->SetVisible(false);
+
+    SubscribeToEvent(listView_, E_ITEMCLICKED, HANDLER(DropDownList, HandleItemClicked));
+    SubscribeToEvent(listView_, E_UNHANDLEDKEY, HANDLER(DropDownList, HandleListViewKey));
+}
+
+DropDownList::~DropDownList()
+{
+}
+
+void DropDownList::RegisterObject(Context* context)
+{
+    context->RegisterFactory<DropDownList>(UI_CATEGORY);
+
+    COPY_BASE_ATTRIBUTES(Menu);
+    UPDATE_ATTRIBUTE_DEFAULT_VALUE("Focus Mode", FM_FOCUSABLE_DEFOCUSABLE);
+    ACCESSOR_ATTRIBUTE("Selection", GetSelection, SetSelectionAttr, unsigned, 0, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Resize Popup", GetResizePopup, SetResizePopup, bool, false, AM_FILE);
+}
+
+void DropDownList::ApplyAttributes()
+{
+    // Reapply selection after possible items have been loaded
+    SetSelection(selectionAttr_);
+}
+
+void DropDownList::GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor)
+{
+    Menu::GetBatches(batches, vertexData, currentScissor);
+
+    if (!placeholder_->IsVisible())
+        return;
+
+    UIElement* selectedItem = GetSelectedItem();
+    if (selectedItem)
+    {
+        // Can not easily copy the selected item. However, it can be re-rendered on the placeholder's position
+        const IntVector2& targetPos = placeholder_->GetScreenPosition();
+        const IntVector2& originalPos = selectedItem->GetScreenPosition();
+        IntVector2 offset = targetPos - originalPos;
+
+        // GetBatches() usually resets the hover flag. Therefore get its value and then reset it for the real rendering
+        // Render the selected item without its selection color, so temporarily reset the item's selected attribute
+        bool hover = selectedItem->IsHovering();
+        selectedItem->SetSelected(false);
+        selectedItem->SetHovering(false);
+        selectedItem->GetBatchesWithOffset(offset, batches, vertexData, currentScissor);
+        selectedItem->SetSelected(true);
+        selectedItem->SetHovering(hover);
+    }
+}
+
+void DropDownList::OnShowPopup()
+{
+    // Resize the popup to match the size of the list content, and optionally match the button width
+    UIElement* content = listView_->GetContentElement();
+    content->UpdateLayout();
+    const IntVector2& contentSize = content->GetSize();
+    const IntRect& border = popup_->GetLayoutBorder();
+    popup_->SetSize(resizePopup_ ? GetWidth() : contentSize.x_ + border.left_ + border.right_,
+        contentSize.y_ + border.top_ + border.bottom_);
+
+    // Check if popup fits below the button. If not, show above instead
+    bool showAbove = false;
+    UIElement* root = GetRoot();
+    if (root)
+    {
+        const IntVector2& screenPos = GetScreenPosition();
+        if (screenPos.y_ + GetHeight() + popup_->GetHeight() > root->GetHeight() && screenPos.y_ - popup_->GetHeight() >= 0)
+            showAbove = true;
+    }
+    SetPopupOffset(0, showAbove ? -popup_->GetHeight() : GetHeight());
+
+    // Focus the ListView to allow making the selection with keys
+    GetSubsystem<SystemUI>()->SetFocusElement(listView_);
+}
+
+void DropDownList::OnHidePopup()
+{
+    // When the popup is hidden, propagate the selection
+    using namespace ItemSelected;
+
+    VariantMap& eventData = GetEventDataMap();
+    eventData[P_ELEMENT] = this;
+    eventData[P_SELECTION] = GetSelection();
+    SendEvent(E_ITEMSELECTED, eventData);
+}
+
+void DropDownList::OnSetEditable()
+{
+    listView_->SetEditable(editable_);
+}
+
+void DropDownList::AddItem(UIElement* item)
+{
+    InsertItem(M_MAX_UNSIGNED, item);
+}
+
+void DropDownList::InsertItem(unsigned index, UIElement* item)
+{
+    listView_->InsertItem(index, item);
+
+    // If there was no selection, set to the first
+    if (listView_->GetSelection() == M_MAX_UNSIGNED)
+        listView_->SetSelection(0);
+}
+
+void DropDownList::RemoveItem(UIElement* item)
+{
+    listView_->RemoveItem(item);
+}
+
+void DropDownList::RemoveItem(unsigned index)
+{
+    listView_->RemoveItem(index);
+}
+
+void DropDownList::RemoveAllItems()
+{
+    listView_->RemoveAllItems();
+}
+
+void DropDownList::SetSelection(unsigned index)
+{
+    listView_->SetSelection(index);
+
+    // Display the place holder text when there is no selection, however, the place holder text is only visible when the place holder itself is set to visible
+    placeholder_->GetChild(0)->SetVisible(index == M_MAX_UNSIGNED);
+}
+
+void DropDownList::SetPlaceholderText(const String& text)
+{
+    static_cast<Text*>(placeholder_->GetChild(0))->SetText(text);
+}
+
+void DropDownList::SetResizePopup(bool enable)
+{
+    resizePopup_ = enable;
+}
+
+unsigned DropDownList::GetNumItems() const
+{
+    return listView_->GetNumItems();
+}
+
+UIElement* DropDownList::GetItem(unsigned index) const
+{
+    return listView_->GetItem(index);
+}
+
+PODVector<UIElement*> DropDownList::GetItems() const
+{
+    return listView_->GetItems();
+}
+
+unsigned DropDownList::GetSelection() const
+{
+    return listView_->GetSelection();
+}
+
+UIElement* DropDownList::GetSelectedItem() const
+{
+    return listView_->GetSelectedItem();
+}
+
+const String& DropDownList::GetPlaceholderText() const
+{
+    return static_cast<Text*>(placeholder_->GetChild(0))->GetText();
+}
+
+void DropDownList::SetSelectionAttr(unsigned index)
+{
+    selectionAttr_ = index;
+
+    // We may not have the list items yet. Apply the index again in ApplyAttributes().
+    SetSelection(index);
+}
+
+bool DropDownList::FilterImplicitAttributes(XMLElement& dest) const
+{
+    if (!Menu::FilterImplicitAttributes(dest))
+        return false;
+
+    if (!RemoveChildXML(dest, "Popup Offset"))
+        return false;
+
+    XMLElement childElem = dest.GetChild("element");
+    if (!childElem)
+        return false;
+    if (!RemoveChildXML(childElem, "Name", "DDL_Placeholder"))
+        return false;
+    if (!RemoveChildXML(childElem, "Size"))
+        return false;
+
+    childElem = childElem.GetChild("element");
+    if (!childElem)
+        return false;
+    if (!RemoveChildXML(childElem, "Name", "DDL_Placeholder_Text"))
+        return false;
+    if (!RemoveChildXML(childElem, "Is Visible"))
+        return false;
+
+    return true;
+}
+
+bool DropDownList::FilterPopupImplicitAttributes(XMLElement& dest) const
+{
+    if (!Menu::FilterPopupImplicitAttributes(dest))
+        return false;
+
+    // Window popup
+    if (dest.GetAttribute("style").Empty() && !dest.SetAttribute("style", "none"))
+        return false;
+    if (!RemoveChildXML(dest, "Layout Mode", "Vertical"))
+        return false;
+    if (!RemoveChildXML(dest, "Size"))
+        return false;
+
+    // ListView
+    XMLElement childElem = dest.GetChild("element");
+    if (!childElem)
+        return false;
+    if (!listView_->FilterAttributes(childElem))
+        return false;
+    if (childElem.GetAttribute("style").Empty() && !childElem.SetAttribute("style", "none"))
+        return false;
+    if (!RemoveChildXML(childElem, "Focus Mode", "NotFocusable"))
+        return false;
+    if (!RemoveChildXML(childElem, "Auto Show/Hide Scrollbars", "false"))
+        return false;
+
+    // Horizontal scroll bar
+    childElem = childElem.GetChild("element");
+    if (childElem && !childElem.GetParent().RemoveChild(childElem))
+        return false;
+
+    // Vertical scroll bar
+    childElem = childElem.GetNext("element");
+    if (childElem && !childElem.GetParent().RemoveChild(childElem))
+        return false;
+
+    // Scroll panel
+    childElem = childElem.GetNext("element");
+    if (!childElem)
+        return false;
+    if (childElem.GetAttribute("style").Empty() && !childElem.SetAttribute("style", "none"))
+        return false;
+
+    // Item container
+    childElem = childElem.GetChild("element");
+    if (!childElem)
+        return false;
+    if (childElem.GetAttribute("style").Empty() && !childElem.SetAttribute("style", "none"))
+        return false;
+
+    return true;
+}
+
+void DropDownList::HandleItemClicked(StringHash eventType, VariantMap& eventData)
+{
+    // Resize the selection placeholder to match the selected item
+    UIElement* selectedItem = GetSelectedItem();
+    if (selectedItem)
+        placeholder_->SetSize(selectedItem->GetSize());
+
+    // Close and defocus the popup. This will actually send the selection forward
+    if (listView_->HasFocus())
+        GetSubsystem<SystemUI>()->SetFocusElement(focusMode_ < FM_FOCUSABLE ? 0 : this);
+    ShowPopup(false);
+}
+
+void DropDownList::HandleListViewKey(StringHash eventType, VariantMap& eventData)
+{
+    using namespace UnhandledKey;
+
+    // If enter pressed in the list view, close and propagate selection
+    int key = eventData[P_KEY].GetInt();
+    if (key == KEY_RETURN || key == KEY_RETURN2 || key == KEY_KP_ENTER)
+        HandleItemClicked(eventType, eventData);
+}
+
+}
+
+}

+ 127 - 0
Source/Atomic/UI/SystemUI/DropDownList.h

@@ -0,0 +1,127 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "Menu.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+class ListView;
+
+/// %Menu %UI element that displays a popup list view.
+class ATOMIC_API DropDownList : public Menu
+{
+    OBJECT(DropDownList)
+
+public:
+    /// Construct.
+    DropDownList(Context* context);
+    /// Destruct.
+    ~DropDownList();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Apply attribute changes that can not be applied immediately.
+    virtual void ApplyAttributes();
+    /// Return UI rendering batches.
+    virtual void GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor);
+    /// React to the popup being shown.
+    virtual void OnShowPopup();
+    /// React to the popup being hidden.
+    virtual void OnHidePopup();
+    /// React to editable status change.
+    virtual void OnSetEditable();
+
+    /// Add item to the end of the list.
+    void AddItem(UIElement* item);
+    /// Insert item to a specific position.
+    void InsertItem(unsigned index, UIElement* item);
+    /// Remove specific item.
+    void RemoveItem(UIElement* item);
+    /// Remove item at index.
+    void RemoveItem(unsigned index);
+    /// Remove all items.
+    void RemoveAllItems();
+    /// Set selection.
+    void SetSelection(unsigned index);
+    /// Set place holder text. This is the text shown when there is no selection in drop down list.
+    void SetPlaceholderText(const String& text);
+    /// Set whether popup should be automatically resized to match the dropdown button width.
+    void SetResizePopup(bool enable);
+
+    /// Return number of items.
+    unsigned GetNumItems() const;
+    /// Return item at index.
+    UIElement* GetItem(unsigned index) const;
+    /// Return all items.
+    PODVector<UIElement*> GetItems() const;
+    /// Return selection index, or M_MAX_UNSIGNED if none selected.
+    unsigned GetSelection() const;
+    /// Return selected item, or null if none selected.
+    UIElement* GetSelectedItem() const;
+
+    /// Return listview element.
+    ListView* GetListView() const { return listView_; }
+
+    /// Return selected item placeholder element.
+    UIElement* GetPlaceholder() const { return placeholder_; }
+
+    /// Return place holder text.
+    const String& GetPlaceholderText() const;
+
+    /// Return whether popup should be automatically resized.
+    bool GetResizePopup() const { return resizePopup_; }
+
+    /// Set selection attribute.
+    void SetSelectionAttr(unsigned index);
+
+protected:
+    /// Filter implicit attributes in serialization process.
+    virtual bool FilterImplicitAttributes(XMLElement& dest) const;
+    /// Filter implicit attributes in serialization process.
+    virtual bool FilterPopupImplicitAttributes(XMLElement& dest) const;
+
+    /// Listview element.
+    SharedPtr<ListView> listView_;
+    /// Selected item placeholder element.
+    SharedPtr<UIElement> placeholder_;
+    /// Resize popup flag.
+    bool resizePopup_;
+
+private:
+    /// Handle listview item click event.
+    void HandleItemClicked(StringHash eventType, VariantMap& eventData);
+    /// Handle a key press from the listview
+    void HandleListViewKey(StringHash eventType, VariantMap& eventData);
+
+    /// Selected item index attribute.
+    unsigned selectionAttr_;
+};
+
+}
+
+}

+ 236 - 0
Source/Atomic/UI/SystemUI/Font.cpp

@@ -0,0 +1,236 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Core/Profiler.h"
+#include "../../Graphics/Graphics.h"
+#include "../../IO/Deserializer.h"
+#include "../../IO/FileSystem.h"
+#include "Font.h"
+#include "FontFaceBitmap.h"
+#include "FontFaceFreeType.h"
+#include "../../Resource/ResourceCache.h"
+#include "../../Resource/XMLElement.h"
+#include "../../Resource/XMLFile.h"
+
+#include "../../DebugNew.h"
+
+namespace Atomic
+{
+
+
+namespace SystemUI
+{
+
+static const int MIN_POINT_SIZE = 1;
+static const int MAX_POINT_SIZE = 96;
+
+Font::Font(Context* context) :
+    Resource(context),
+    fontDataSize_(0),
+    absoluteOffset_(IntVector2::ZERO),
+    scaledOffset_(Vector2::ZERO),
+    fontType_(FONT_NONE),
+    sdfFont_(false)
+{
+}
+
+Font::~Font()
+{
+    // To ensure FreeType deallocates properly, first clear all faces, then release the raw font data
+    ReleaseFaces();
+    fontData_.Reset();
+}
+
+void Font::RegisterObject(Context* context)
+{
+    context->RegisterFactory<Font>();
+}
+
+bool Font::BeginLoad(Deserializer& source)
+{
+    // In headless mode, do not actually load, just return success
+    Graphics* graphics = GetSubsystem<Graphics>();
+    if (!graphics)
+        return true;
+
+    fontType_ = FONT_NONE;
+    faces_.Clear();
+
+    fontDataSize_ = source.GetSize();
+    if (fontDataSize_)
+    {
+        fontData_ = new unsigned char[fontDataSize_];
+        if (source.Read(&fontData_[0], fontDataSize_) != fontDataSize_)
+            return false;
+    }
+    else
+    {
+        fontData_.Reset();
+        return false;
+    }
+
+    String ext = GetExtension(GetName());
+    if (ext == ".ttf" || ext == ".otf" || ext == ".woff")
+    {
+        fontType_ = FONT_FREETYPE;
+        LoadParameters();
+    }
+    else if (ext == ".xml" || ext == ".fnt" || ext == ".sdf")
+        fontType_ = FONT_BITMAP;
+
+    sdfFont_ = ext == ".sdf";
+
+    SetMemoryUse(fontDataSize_);
+    return true;
+}
+
+bool Font::SaveXML(Serializer& dest, int pointSize, bool usedGlyphs, const String& indentation)
+{
+    FontFace* fontFace = GetFace(pointSize);
+    if (!fontFace)
+        return false;
+
+    PROFILE(FontSaveXML);
+
+    SharedPtr<FontFaceBitmap> packedFontFace(new FontFaceBitmap(this));
+    if (!packedFontFace->Load(fontFace, usedGlyphs))
+        return false;
+
+    return packedFontFace->Save(dest, pointSize, indentation);
+}
+
+void Font::SetAbsoluteGlyphOffset(const IntVector2& offset)
+{
+    absoluteOffset_ = offset;
+}
+
+void Font::SetScaledGlyphOffset(const Vector2& offset)
+{
+    scaledOffset_ = offset;
+}
+
+FontFace* Font::GetFace(int pointSize)
+{
+    // In headless mode, always return null
+    Graphics* graphics = GetSubsystem<Graphics>();
+    if (!graphics)
+        return 0;
+
+    // For bitmap font type, always return the same font face provided by the font's bitmap file regardless of the actual requested point size
+    if (fontType_ == FONT_BITMAP)
+        pointSize = 0;
+    else
+        pointSize = Clamp(pointSize, MIN_POINT_SIZE, MAX_POINT_SIZE);
+
+    HashMap<int, SharedPtr<FontFace> >::Iterator i = faces_.Find(pointSize);
+    if (i != faces_.End())
+    {
+        if (!i->second_->IsDataLost())
+            return i->second_;
+        else
+        {
+            // Erase and reload face if texture data lost (OpenGL mode only)
+            faces_.Erase(i);
+        }
+    }
+
+    PROFILE(GetFontFace);
+
+    switch (fontType_)
+    {
+    case FONT_FREETYPE:
+        return GetFaceFreeType(pointSize);
+
+    case FONT_BITMAP:
+        return GetFaceBitmap(pointSize);
+
+    default:
+        return 0;
+    }
+}
+
+IntVector2 Font::GetTotalGlyphOffset(int pointSize) const
+{
+    Vector2 multipliedOffset = (float)pointSize * scaledOffset_;
+    return absoluteOffset_ + IntVector2((int)multipliedOffset.x_, (int)multipliedOffset.y_);
+}
+
+void Font::ReleaseFaces()
+{
+    faces_.Clear();
+}
+
+void Font::LoadParameters()
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    String xmlName = ReplaceExtension(GetName(), ".xml");
+    SharedPtr<XMLFile> xml = cache->GetTempResource<XMLFile>(xmlName, false);
+    if (!xml)
+        return;
+
+    XMLElement rootElem = xml->GetRoot();
+
+    XMLElement absoluteElem = rootElem.GetChild("absoluteoffset");
+    if (!absoluteElem)
+        absoluteElem = rootElem.GetChild("absolute");
+
+    if (absoluteElem)
+    {
+        absoluteOffset_.x_ = absoluteElem.GetInt("x");
+        absoluteOffset_.y_ = absoluteElem.GetInt("y");
+    }
+
+    XMLElement scaledElem = rootElem.GetChild("scaledoffset");
+    if (!scaledElem)
+        scaledElem = rootElem.GetChild("scaled");
+
+    if (scaledElem)
+    {
+        scaledOffset_.x_ = scaledElem.GetFloat("x");
+        scaledOffset_.y_ = scaledElem.GetFloat("y");
+    }
+}
+
+FontFace* Font::GetFaceFreeType(int pointSize)
+{
+    SharedPtr<FontFace> newFace(new FontFaceFreeType(this));
+    if (!newFace->Load(&fontData_[0], fontDataSize_, pointSize))
+        return 0;
+
+    faces_[pointSize] = newFace;
+    return newFace;
+}
+
+FontFace* Font::GetFaceBitmap(int pointSize)
+{
+    SharedPtr<FontFace> newFace(new FontFaceBitmap(this));
+    if (!newFace->Load(&fontData_[0], fontDataSize_, pointSize))
+        return 0;
+
+    faces_[pointSize] = newFace;
+    return newFace;
+}
+
+}
+
+}

+ 114 - 0
Source/Atomic/UI/SystemUI/Font.h

@@ -0,0 +1,114 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "../../Container/ArrayPtr.h"
+#include "../../Resource/Resource.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+class FontFace;
+
+static const int FONT_TEXTURE_MIN_SIZE = 128;
+static const int FONT_DPI = 96;
+
+/// %Font file type.
+enum FONT_TYPE
+{
+    FONT_NONE = 0,
+    FONT_FREETYPE,
+    FONT_BITMAP,
+    MAX_FONT_TYPES
+};
+
+/// %Font resource.
+class ATOMIC_API Font : public Resource
+{
+    OBJECT(Font);
+
+public:
+    /// Construct.
+    Font(Context* context);
+    /// Destruct.
+    virtual ~Font();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Load resource from stream. May be called from a worker thread. Return true if successful.
+    virtual bool BeginLoad(Deserializer& source);
+    /// Save resource as a new bitmap font type in XML format. Return true if successful.
+    bool SaveXML(Serializer& dest, int pointSize, bool usedGlyphs = false, const String& indentation = "\t");
+    /// Set absolute (in pixels) position adjustment for glyphs.
+    void SetAbsoluteGlyphOffset(const IntVector2& offset);
+    /// Set point size scaled position adjustment for glyphs.
+    void SetScaledGlyphOffset(const Vector2& offset);
+
+    /// Return font face. Pack and render to a texture if not rendered yet. Return null on error.
+    FontFace* GetFace(int pointSize);
+
+    /// Is signed distance field font.
+    bool IsSDFFont() const { return sdfFont_; }
+
+    /// Return absolute position adjustment for glyphs.
+    const IntVector2& GetAbsoluteGlyphOffset() const { return absoluteOffset_; }
+
+    /// Return point size scaled position adjustment for glyphs.
+    const Vector2& GetScaledGlyphOffset() const { return scaledOffset_; }
+
+    /// Return the total effective offset for a point size.
+    IntVector2 GetTotalGlyphOffset(int pointSize) const;
+
+    /// Release font faces and recreate them next time when requested. Called when font textures lost or global font properties change.
+    void ReleaseFaces();
+
+private:
+    /// Load font glyph offset parameters from an optional XML file. Called internally when loading TrueType fonts.
+    void LoadParameters();
+    /// Return font face using FreeType. Called internally. Return null on error.
+    FontFace* GetFaceFreeType(int pointSize);
+    /// Return bitmap font face. Called internally. Return null on error.
+    FontFace* GetFaceBitmap(int pointSize);
+
+    /// Created faces.
+    HashMap<int, SharedPtr<FontFace> > faces_;
+    /// Font data.
+    SharedArrayPtr<unsigned char> fontData_;
+    /// Size of font data.
+    unsigned fontDataSize_;
+    /// Absolute position adjustment for glyphs.
+    IntVector2 absoluteOffset_;
+    /// Point size scaled position adjustment for glyphs.
+    Vector2 scaledOffset_;
+    /// Font type.
+    FONT_TYPE fontType_;
+    /// Signed distance field font flag.
+    bool sdfFont_;
+};
+
+}
+
+}

+ 130 - 0
Source/Atomic/UI/SystemUI/FontFace.cpp

@@ -0,0 +1,130 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../IO/Log.h"
+#include "../../Graphics/Texture2D.h"
+#include "../../Resource/Image.h"
+#include "Font.h"
+#include "FontFace.h"
+
+#include "../../DebugNew.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+
+FontGlyph::FontGlyph() :
+    page_(M_MAX_UNSIGNED),
+    used_(false)
+{
+}
+
+FontFace::FontFace(Font* font) :
+    font_(font)
+{
+}
+
+FontFace::~FontFace()
+{
+    if (font_)
+    {
+        // When a face is unloaded, deduct the used texture data size from the parent font
+        unsigned totalTextureSize = 0;
+        for (unsigned i = 0; i < textures_.Size(); ++i)
+            totalTextureSize += textures_[i]->GetWidth() * textures_[i]->GetHeight();
+        font_->SetMemoryUse(font_->GetMemoryUse() - totalTextureSize);
+    }
+}
+
+const FontGlyph* FontFace::GetGlyph(unsigned c)
+{
+    HashMap<unsigned, FontGlyph>::Iterator i = glyphMapping_.Find(c);
+    if (i != glyphMapping_.End())
+    {
+        FontGlyph& glyph = i->second_;
+        glyph.used_ = true;
+        return &glyph;
+    }
+    else
+        return 0;
+}
+
+short FontFace::GetKerning(unsigned c, unsigned d) const
+{
+    if (kerningMapping_.Empty())
+        return 0;
+
+    if (c == '\n' || d == '\n')
+        return 0;
+
+    if (c > 0xffff || d > 0xffff)
+        return 0;
+
+    unsigned value = (c << 16) + d;
+
+    HashMap<unsigned, short>::ConstIterator i = kerningMapping_.Find(value);
+    if (i != kerningMapping_.End())
+        return i->second_;
+
+    return 0;
+}
+
+bool FontFace::IsDataLost() const
+{
+    for (unsigned i = 0; i < textures_.Size(); ++i)
+    {
+        if (textures_[i]->IsDataLost())
+            return true;
+    }
+    return false;
+}
+
+
+SharedPtr<Texture2D> FontFace::CreateFaceTexture()
+{
+    SharedPtr<Texture2D> texture(new Texture2D(font_->GetContext()));
+    texture->SetMipsToSkip(QUALITY_LOW, 0); // No quality reduction
+    texture->SetNumLevels(1); // No mipmaps
+    texture->SetAddressMode(COORD_U, ADDRESS_BORDER);
+    texture->SetAddressMode(COORD_V, ADDRESS_BORDER),
+        texture->SetBorderColor(Color(0.0f, 0.0f, 0.0f, 0.0f));
+    return texture;
+}
+
+SharedPtr<Texture2D> FontFace::LoadFaceTexture(SharedPtr<Image> image)
+{
+    SharedPtr<Texture2D> texture = CreateFaceTexture();
+    if (!texture->SetData(image, true))
+    {
+        LOGERROR("Could not load texture from image resource");
+        return SharedPtr<Texture2D>();
+    }
+    return texture;
+}
+
+}
+
+}

+ 124 - 0
Source/Atomic/UI/SystemUI/FontFace.h

@@ -0,0 +1,124 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "../../Container/ArrayPtr.h"
+#include "../../Container/List.h"
+#include "../../Math/AreaAllocator.h"
+
+namespace Atomic
+{
+
+class Image;
+class Texture2D;
+
+namespace SystemUI
+{
+
+class Font;
+
+/// %Font glyph description.
+struct FontGlyph
+{
+    /// Construct.
+    FontGlyph();
+
+    /// X position in texture.
+    short x_;
+    /// Y position in texture.
+    short y_;
+    /// Width.
+    short width_;
+    /// Height.
+    short height_;
+    /// Glyph X offset from origin.
+    short offsetX_;
+    /// Glyph Y offset from origin.
+    short offsetY_;
+    /// Horizontal advance.
+    short advanceX_;
+    /// Texture page. M_MAX_UNSIGNED if not yet resident on any texture.
+    unsigned page_;
+    /// Used flag.
+    bool used_;
+};
+
+/// %Font face description.
+class ATOMIC_API FontFace : public RefCounted
+{
+    friend class Font;
+
+    REFCOUNTED(FontFace)
+
+public:
+    /// Construct.
+    FontFace(Font* font);
+    /// Destruct.
+    ~FontFace();
+
+    /// Load font face.
+    virtual bool Load(const unsigned char* fontData, unsigned fontDataSize, int pointSize) = 0;
+    /// Return pointer to the glyph structure corresponding to a character. Return null if glyph not found.
+    virtual const FontGlyph* GetGlyph(unsigned c);
+
+    /// Return if font face uses mutable glyphs.
+    virtual bool HasMutableGlyphs() const { return false; }
+
+    /// Return the kerning for a character and the next character.
+    short GetKerning(unsigned c, unsigned d) const;
+    /// Return true when one of the texture has a data loss.
+    bool IsDataLost() const;
+
+    /// Return point size.
+    int GetPointSize() const { return pointSize_; }
+
+    /// Return row height.
+    int GetRowHeight() const { return rowHeight_; }
+
+    /// Return textures.
+    const Vector<SharedPtr<Texture2D> >& GetTextures() const { return textures_; }
+
+protected:
+    friend class FontFaceBitmap;
+    /// Create a texture for font rendering.
+    SharedPtr<Texture2D> CreateFaceTexture();
+    /// Load font face texture from image resource.
+    SharedPtr<Texture2D> LoadFaceTexture(SharedPtr<Image> image);
+
+    /// Parent font.
+    Font* font_;
+    /// Glyph mapping.
+    HashMap<unsigned, FontGlyph> glyphMapping_;
+    /// Kerning mapping.
+    HashMap<unsigned, short> kerningMapping_;
+    /// Glyph texture pages.
+    Vector<SharedPtr<Texture2D> > textures_;
+    /// Point size.
+    int pointSize_;
+    /// Row height.
+    int rowHeight_;
+};
+
+}
+
+}

+ 391 - 0
Source/Atomic/UI/SystemUI/FontFaceBitmap.cpp

@@ -0,0 +1,391 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Graphics/Graphics.h"
+#include "../../Graphics/Texture2D.h"
+#include "../../IO/File.h"
+#include "../../IO/FileSystem.h"
+#include "../../IO/Log.h"
+#include "../../IO/MemoryBuffer.h"
+#include "../../Resource/ResourceCache.h"
+#include "Font.h"
+#include "FontFaceBitmap.h"
+#include "SystemUI.h"
+
+#include "../../DebugNew.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+
+FontFaceBitmap::FontFaceBitmap(Font* font) :
+    FontFace(font)
+{
+}
+
+FontFaceBitmap::~FontFaceBitmap()
+{
+}
+
+bool FontFaceBitmap::Load(const unsigned char* fontData, unsigned fontDataSize, int pointSize)
+{
+    Context* context = font_->GetContext();
+
+    SharedPtr<XMLFile> xmlReader(new XMLFile(context));
+    MemoryBuffer memoryBuffer(fontData, fontDataSize);
+    if (!xmlReader->Load(memoryBuffer))
+    {
+        LOGERROR("Could not load XML file");
+        return false;
+    }
+
+    XMLElement root = xmlReader->GetRoot("font");
+    if (root.IsNull())
+    {
+        LOGERROR("Could not find Font element");
+        return false;
+    }
+
+    XMLElement pagesElem = root.GetChild("pages");
+    if (pagesElem.IsNull())
+    {
+        LOGERROR("Could not find Pages element");
+        return false;
+    }
+
+    XMLElement infoElem = root.GetChild("info");
+    if (!infoElem.IsNull())
+        pointSize_ = infoElem.GetInt("size");
+
+    XMLElement commonElem = root.GetChild("common");
+    rowHeight_ = commonElem.GetInt("lineHeight");
+    unsigned pages = commonElem.GetUInt("pages");
+    textures_.Reserve(pages);
+
+    ResourceCache* resourceCache = font_->GetSubsystem<ResourceCache>();
+    String fontPath = GetPath(font_->GetName());
+    unsigned totalTextureSize = 0;
+
+    XMLElement pageElem = pagesElem.GetChild("page");
+    for (unsigned i = 0; i < pages; ++i)
+    {
+        if (pageElem.IsNull())
+        {
+            LOGERROR("Could not find Page element for page: " + String(i));
+            return 0;
+        }
+
+        // Assume the font image is in the same directory as the font description file
+        String textureFile = fontPath + pageElem.GetAttribute("file");
+
+        // Load texture manually to allow controlling the alpha channel mode
+        SharedPtr<File> fontFile = resourceCache->GetFile(textureFile);
+        SharedPtr<Image> fontImage(new Image(context));
+        if (!fontFile || !fontImage->Load(*fontFile))
+        {
+            LOGERROR("Failed to load font image file");
+            return 0;
+        }
+        SharedPtr<Texture2D> texture = LoadFaceTexture(fontImage);
+        if (!texture)
+            return 0;
+
+        textures_.Push(texture);
+
+        // Add texture to resource cache
+        texture->SetName(fontFile->GetName());
+        resourceCache->AddManualResource(texture);
+
+        totalTextureSize += fontImage->GetWidth() * fontImage->GetHeight() * fontImage->GetComponents();
+
+        pageElem = pageElem.GetNext("page");
+    }
+
+    XMLElement charsElem = root.GetChild("chars");
+    int count = charsElem.GetInt("count");
+
+    XMLElement charElem = charsElem.GetChild("char");
+    while (!charElem.IsNull())
+    {
+        int id = charElem.GetInt("id");
+
+        FontGlyph glyph;
+        glyph.x_ = (short)charElem.GetInt("x");
+        glyph.y_ = (short)charElem.GetInt("y");
+        glyph.width_ = (short)charElem.GetInt("width");
+        glyph.height_ = (short)charElem.GetInt("height");
+        glyph.offsetX_ = (short)charElem.GetInt("xoffset");
+        glyph.offsetY_ = (short)charElem.GetInt("yoffset");
+        glyph.advanceX_ = (short)charElem.GetInt("xadvance");
+        glyph.page_ = charElem.GetUInt("page");
+
+        glyphMapping_[id] = glyph;
+
+        charElem = charElem.GetNext("char");
+    }
+
+    XMLElement kerningsElem = root.GetChild("kernings");
+    if (kerningsElem.NotNull())
+    {
+        XMLElement kerningElem = kerningsElem.GetChild("kerning");
+        while (!kerningElem.IsNull())
+        {
+            int first = kerningElem.GetInt("first");
+            int second = kerningElem.GetInt("second");
+            unsigned value = (unsigned)((first << 16) + second);
+            kerningMapping_[value] = (short)kerningElem.GetInt("amount");
+
+            kerningElem = kerningElem.GetNext("kerning");
+        }
+    }
+
+    LOGDEBUGF("Bitmap font face %s has %d glyphs", GetFileName(font_->GetName()).CString(), count);
+
+    font_->SetMemoryUse(font_->GetMemoryUse() + totalTextureSize);
+    return true;
+}
+
+bool FontFaceBitmap::Load(FontFace* fontFace, bool usedGlyphs)
+{
+    if (this == fontFace)
+        return true;
+
+    if (!usedGlyphs)
+    {
+        glyphMapping_ = fontFace->glyphMapping_;
+        kerningMapping_ = fontFace->kerningMapping_;
+        textures_ = fontFace->textures_;
+        pointSize_ = fontFace->pointSize_;
+        rowHeight_ = fontFace->rowHeight_;
+
+        return true;
+    }
+
+    pointSize_ = fontFace->pointSize_;
+    rowHeight_ = fontFace->rowHeight_;
+
+    unsigned numPages = 1;
+    int maxTextureSize = font_->GetSubsystem<SystemUI>()->GetMaxFontTextureSize();
+    AreaAllocator allocator(FONT_TEXTURE_MIN_SIZE, FONT_TEXTURE_MIN_SIZE, maxTextureSize, maxTextureSize);
+
+    for (HashMap<unsigned, FontGlyph>::ConstIterator i = fontFace->glyphMapping_.Begin(); i != fontFace->glyphMapping_.End(); ++i)
+    {
+        FontGlyph fontGlyph = i->second_;
+        if (!fontGlyph.used_)
+            continue;
+
+        int x, y;
+        if (!allocator.Allocate(fontGlyph.width_ + 1, fontGlyph.height_ + 1, x, y))
+        {
+            ++numPages;
+
+            allocator = AreaAllocator(FONT_TEXTURE_MIN_SIZE, FONT_TEXTURE_MIN_SIZE, maxTextureSize, maxTextureSize);
+            if (!allocator.Allocate(fontGlyph.width_ + 1, fontGlyph.height_ + 1, x, y))
+                return false;
+        }
+
+        fontGlyph.x_ = (short)x;
+        fontGlyph.y_ = (short)y;
+        fontGlyph.page_ = numPages - 1;
+
+        glyphMapping_[i->first_] = fontGlyph;
+    }
+
+    // Assume that format is the same for all textures and that bitmap font type may have more than one component
+    unsigned components = ConvertFormatToNumComponents(fontFace->textures_[0]->GetFormat());
+
+    // Save the existing textures as image resources
+    Vector<SharedPtr<Image> > oldImages;
+    for (unsigned i = 0; i < fontFace->textures_.Size(); ++i)
+        oldImages.Push(SaveFaceTexture(fontFace->textures_[i]));
+
+    Vector<SharedPtr<Image> > newImages(numPages);
+    for (unsigned i = 0; i < numPages; ++i)
+    {
+        SharedPtr<Image> image(new Image(font_->GetContext()));
+
+        int width = maxTextureSize;
+        int height = maxTextureSize;
+        if (i == numPages - 1)
+        {
+            width = allocator.GetWidth();
+            height = allocator.GetHeight();
+        }
+
+        image->SetSize(width, height, components);
+        memset(image->GetData(), 0, width * height * components);
+
+        newImages.Push(image);
+    }
+
+    for (HashMap<unsigned, FontGlyph>::Iterator i = glyphMapping_.Begin(); i != glyphMapping_.End(); ++i)
+    {
+        FontGlyph& newGlyph = i->second_;
+        const FontGlyph& oldGlyph = fontFace->glyphMapping_[i->first_];
+        Blit(newImages[newGlyph.page_], newGlyph.x_, newGlyph.y_, newGlyph.width_, newGlyph.height_, oldImages[oldGlyph.page_],
+            oldGlyph.x_, oldGlyph.y_, components);
+    }
+
+    textures_.Resize(newImages.Size());
+    for (unsigned i = 0; i < newImages.Size(); ++i)
+        textures_[i] = LoadFaceTexture(newImages[i]);
+
+    for (HashMap<unsigned, short>::ConstIterator i = fontFace->kerningMapping_.Begin(); i != fontFace->kerningMapping_.End(); ++i)
+    {
+        unsigned first = (i->first_) >> 16;
+        unsigned second = (i->first_) & 0xffff;
+        if (glyphMapping_.Find(first) != glyphMapping_.End() && glyphMapping_.Find(second) != glyphMapping_.End())
+            kerningMapping_[i->first_] = i->second_;
+    }
+
+    return true;
+}
+
+bool FontFaceBitmap::Save(Serializer& dest, int pointSize, const String& indentation)
+{
+    Context* context = font_->GetContext();
+
+    SharedPtr<XMLFile> xml(new XMLFile(context));
+    XMLElement rootElem = xml->CreateRoot("font");
+
+    // Information
+    XMLElement childElem = rootElem.CreateChild("info");
+    String fileName = GetFileName(font_->GetName());
+    childElem.SetAttribute("face", fileName);
+    childElem.SetAttribute("size", String(pointSize));
+
+    // Common
+    childElem = rootElem.CreateChild("common");
+    childElem.SetInt("lineHeight", rowHeight_);
+    unsigned pages = textures_.Size();
+    childElem.SetUInt("pages", pages);
+
+    // Construct the path to store the texture
+    String pathName;
+    File* file = dynamic_cast<File*>(&dest);
+    if (file)
+        // If serialize to file, use the file's path
+        pathName = GetPath(file->GetName());
+    else
+        // Otherwise, use the font resource's path
+        pathName = "Data/" + GetPath(font_->GetName());
+
+    // Pages
+    childElem = rootElem.CreateChild("pages");
+    for (unsigned i = 0; i < pages; ++i)
+    {
+        XMLElement pageElem = childElem.CreateChild("page");
+        pageElem.SetInt("id", i);
+        String texFileName = fileName + "_" + String(i) + ".png";
+        pageElem.SetAttribute("file", texFileName);
+
+        // Save the font face texture to image file
+        SaveFaceTexture(textures_[i], pathName + texFileName);
+    }
+
+    // Chars and kernings
+    XMLElement charsElem = rootElem.CreateChild("chars");
+    unsigned numGlyphs = glyphMapping_.Size();
+    charsElem.SetInt("count", numGlyphs);
+
+    for (HashMap<unsigned, FontGlyph>::ConstIterator i = glyphMapping_.Begin(); i != glyphMapping_.End(); ++i)
+    {
+        // Char
+        XMLElement charElem = charsElem.CreateChild("char");
+        charElem.SetInt("id", i->first_);
+
+        const FontGlyph& glyph = i->second_;
+        charElem.SetInt("x", glyph.x_);
+        charElem.SetInt("y", glyph.y_);
+        charElem.SetInt("width", glyph.width_);
+        charElem.SetInt("height", glyph.height_);
+        charElem.SetInt("xoffset", glyph.offsetX_);
+        charElem.SetInt("yoffset", glyph.offsetY_);
+        charElem.SetInt("xadvance", glyph.advanceX_);
+        charElem.SetUInt("page", glyph.page_);
+    }
+
+    if (!kerningMapping_.Empty())
+    {
+        XMLElement kerningsElem = rootElem.CreateChild("kernings");
+        for (HashMap<unsigned, short>::ConstIterator i = kerningMapping_.Begin(); i != kerningMapping_.End(); ++i)
+        {
+            XMLElement kerningElem = kerningsElem.CreateChild("kerning");
+            kerningElem.SetInt("first", i->first_ >> 16);
+            kerningElem.SetInt("second", i->first_ & 0xffff);
+            kerningElem.SetInt("amount", i->second_);
+        }
+    }
+
+    return xml->Save(dest, indentation);
+}
+
+unsigned FontFaceBitmap::ConvertFormatToNumComponents(unsigned format)
+{
+    if (format == Graphics::GetRGBAFormat())
+        return 4;
+    else if (format == Graphics::GetRGBFormat())
+        return 3;
+    else if (format == Graphics::GetLuminanceAlphaFormat())
+        return 2;
+    else
+        return 1;
+}
+
+SharedPtr<Image> FontFaceBitmap::SaveFaceTexture(Texture2D* texture)
+{
+    Image* image = new Image(font_->GetContext());
+    image->SetSize(texture->GetWidth(), texture->GetHeight(), ConvertFormatToNumComponents(texture->GetFormat()));
+    if (!texture->GetData(0, image->GetData()))
+    {
+        delete image;
+        LOGERROR("Could not save texture to image resource");
+        return SharedPtr<Image>();
+    }
+    return SharedPtr<Image>(image);
+}
+
+bool FontFaceBitmap::SaveFaceTexture(Texture2D* texture, const String& fileName)
+{
+    SharedPtr<Image> image = SaveFaceTexture(texture);
+    return image ? image->SavePNG(fileName) : false;
+}
+
+void FontFaceBitmap::Blit(Image* dest, int x, int y, int width, int height, Image* source, int sourceX, int sourceY, int components)
+{
+    unsigned char* destData = dest->GetData() + (y * dest->GetWidth() + x) * components;
+    unsigned char* sourceData = source->GetData() + (sourceY * source->GetWidth() + sourceX) * components;
+    for (int i = 0; i < height; ++i)
+    {
+        memcpy(destData, sourceData, (size_t)(width * components));
+        destData += dest->GetWidth() * components;
+        sourceData += source->GetWidth() * components;
+    }
+}
+
+}
+
+}

+ 68 - 0
Source/Atomic/UI/SystemUI/FontFaceBitmap.h

@@ -0,0 +1,68 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "FontFace.h"
+
+namespace Atomic
+{
+
+class Image;
+class Serializer;
+
+namespace SystemUI
+{
+
+
+/// Bitmap font face description.
+class ATOMIC_API FontFaceBitmap : public FontFace
+{
+    REFCOUNTED(FontFaceBitmap)
+
+public:
+    /// Construct.
+    FontFaceBitmap(Font* font);
+    /// Destruct.
+    ~FontFaceBitmap();
+
+    /// Load font face.
+    virtual bool Load(const unsigned char* fontData, unsigned fontDataSize, int pointSize);
+    /// Load from existed font face, pack used glyphs into smallest texture size and smallest number of texture.
+    bool Load(FontFace* fontFace, bool usedGlyphs);
+    /// Save as a new bitmap font type in XML format. Return true if successful.
+    bool Save(Serializer& dest, int pointSize, const String& indentation = "\t");
+
+private:
+    /// Convert graphics format to number of components.
+    unsigned ConvertFormatToNumComponents(unsigned format);
+    /// Save font face texture as image resource.
+    SharedPtr<Image> SaveFaceTexture(Texture2D* texture);
+    /// Save font face texture as image file.
+    bool SaveFaceTexture(Texture2D* texture, const String& fileName);
+    /// Blit.
+    void Blit(Image* dest, int x, int y, int width, int height, Image* source, int sourceX, int sourceY, int components);
+};
+
+}
+
+}

+ 459 - 0
Source/Atomic/UI/SystemUI/FontFaceFreeType.cpp

@@ -0,0 +1,459 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Graphics/Graphics.h"
+#include "../../Graphics/Texture2D.h"
+#include "../../IO/FileSystem.h"
+#include "../../IO/Log.h"
+#include "../../IO/MemoryBuffer.h"
+#include "Font.h"
+#include "FontFaceFreeType.h"
+#include "SystemUI.h"
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_TRUETYPE_TABLES_H
+
+#include "../../DebugNew.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+
+/// FreeType library subsystem.
+class FreeTypeLibrary : public Object
+{
+    OBJECT(FreeTypeLibrary);
+
+public:
+    /// Construct.
+    FreeTypeLibrary(Context* context) :
+        Object(context)
+    {
+        FT_Error error = FT_Init_FreeType(&library_);
+        if (error)
+            LOGERROR("Could not initialize FreeType library");
+    }
+
+    /// Destruct.
+    virtual ~FreeTypeLibrary()
+    {
+        FT_Done_FreeType(library_);
+    }
+
+    FT_Library GetLibrary() const { return library_; }
+
+private:
+    /// FreeType library.
+    FT_Library library_;
+};
+
+FontFaceFreeType::FontFaceFreeType(Font* font) :
+    FontFace(font),
+    face_(0),
+    loadMode_(FT_LOAD_DEFAULT)
+{
+}
+
+FontFaceFreeType::~FontFaceFreeType()
+{
+    if (face_)
+    {
+        FT_Done_Face((FT_Face)face_);
+        face_ = 0;
+    }
+}
+
+bool FontFaceFreeType::Load(const unsigned char* fontData, unsigned fontDataSize, int pointSize)
+{
+    Context* context = font_->GetContext();
+
+    // Create & initialize FreeType library if it does not exist yet
+    FreeTypeLibrary* freeType = font_->GetSubsystem<FreeTypeLibrary>();
+    if (!freeType)
+        context->RegisterSubsystem(freeType = new FreeTypeLibrary(context));
+
+    // Ensure the FreeType library is kept alive as long as TTF font resources exist
+    freeType_ = freeType;
+
+    SystemUI* ui = font_->GetSubsystem<SystemUI>();
+    int maxTextureSize = ui->GetMaxFontTextureSize();
+
+    FT_Face face;
+    FT_Error error;
+    FT_Library library = freeType->GetLibrary();
+
+    if (pointSize <= 0)
+    {
+        LOGERROR("Zero or negative point size");
+        return false;
+    }
+
+    if (!fontDataSize)
+    {
+        LOGERROR("Could not create font face from zero size data");
+        return false;
+    }
+
+    error = FT_New_Memory_Face(library, fontData, fontDataSize, 0, &face);
+    if (error)
+    {
+        LOGERROR("Could not create font face");
+        return false;
+    }
+    error = FT_Set_Char_Size(face, 0, pointSize * 64, FONT_DPI, FONT_DPI);
+    if (error)
+    {
+        FT_Done_Face(face);
+        LOGERROR("Could not set font point size " + String(pointSize));
+        return false;
+    }
+
+    face_ = face;
+
+    unsigned numGlyphs = (unsigned)face->num_glyphs;
+    LOGDEBUGF("Font face %s (%dpt) has %d glyphs", GetFileName(font_->GetName()).CString(), pointSize, numGlyphs);
+
+    PODVector<unsigned> charCodes(numGlyphs);
+    for (unsigned i = 0; i < numGlyphs; ++i)
+        charCodes[i] = 0;
+
+    FT_UInt glyphIndex;
+    FT_ULong charCode = FT_Get_First_Char(face, &glyphIndex);
+    while (glyphIndex != 0)
+    {
+        if (glyphIndex < numGlyphs)
+            charCodes[glyphIndex] = (unsigned)charCode;
+
+        charCode = FT_Get_Next_Char(face, charCode, &glyphIndex);
+    }
+
+    // Load each of the glyphs to see the sizes & store other information
+    loadMode_ = (int)(ui->GetForceAutoHint() ? FT_LOAD_FORCE_AUTOHINT : FT_LOAD_DEFAULT);
+    ascender_ = (int)(face->size->metrics.ascender >> 6);
+    int descender = (int)(face->size->metrics.descender >> 6);
+
+    // Check if the font's OS/2 info gives different (larger) values for ascender & descender
+    TT_OS2* os2Info = (TT_OS2*)FT_Get_Sfnt_Table(face, ft_sfnt_os2);
+    if (os2Info)
+    {
+        ascender_ = Max(ascender_, os2Info->usWinAscent * face->size->metrics.y_ppem / face->units_per_EM);
+        ascender_ = Max(ascender_, os2Info->sTypoAscender * face->size->metrics.y_ppem / face->units_per_EM);
+        descender = Max(descender, os2Info->usWinDescent * face->size->metrics.y_ppem / face->units_per_EM);
+        descender = Max(descender, os2Info->sTypoDescender * face->size->metrics.y_ppem / face->units_per_EM);
+    }
+
+    // Store point size and row height. Use the maximum of ascender + descender, or the face's stored default row height
+    pointSize_ = pointSize;
+    rowHeight_ = (int)Max(ascender_ + descender, face->size->metrics.height >> 6);
+
+    int textureWidth = maxTextureSize;
+    int textureHeight = maxTextureSize;
+    bool loadAllGlyphs = CanLoadAllGlyphs(charCodes, textureWidth, textureHeight);
+
+    SharedPtr<Image> image(new Image(font_->GetContext()));
+    image->SetSize(textureWidth, textureHeight, 1);
+    unsigned char* imageData = image->GetData();
+    memset(imageData, 0, (size_t)(image->GetWidth() * image->GetHeight()));
+    allocator_.Reset(FONT_TEXTURE_MIN_SIZE, FONT_TEXTURE_MIN_SIZE, textureWidth, textureHeight);
+
+    for (unsigned i = 0; i < numGlyphs; ++i)
+    {
+        unsigned charCode = charCodes[i];
+        if (charCode == 0)
+            continue;
+
+        if (!loadAllGlyphs && (charCode > 0xff))
+            break;
+
+        if (!LoadCharGlyph(charCode, image))
+            return false;
+    }
+
+    SharedPtr<Texture2D> texture = LoadFaceTexture(image);
+    if (!texture)
+        return false;
+
+    textures_.Push(texture);
+    font_->SetMemoryUse(font_->GetMemoryUse() + textureWidth * textureHeight);
+
+    // Store kerning if face has kerning information
+    if (FT_HAS_KERNING(face))
+    {
+        // Read kerning manually to be more efficient and avoid out of memory crash when use large font file, for example there
+        // are 29354 glyphs in msyh.ttf
+        FT_ULong tagKern = FT_MAKE_TAG('k', 'e', 'r', 'n');
+        FT_ULong kerningTableSize = 0;
+        FT_Error error = FT_Load_Sfnt_Table(face, tagKern, 0, NULL, &kerningTableSize);
+        if (error)
+        {
+            LOGERROR("Could not get kerning table length");
+            return false;
+        }
+
+        SharedArrayPtr<unsigned char> kerningTable(new unsigned char[kerningTableSize]);
+        error = FT_Load_Sfnt_Table(face, tagKern, 0, kerningTable, &kerningTableSize);
+        if (error)
+        {
+            LOGERROR("Could not load kerning table");
+            return false;
+        }
+
+        // Convert big endian to little endian
+        for (unsigned i = 0; i < kerningTableSize; i += 2)
+            Swap(kerningTable[i], kerningTable[i + 1]);
+        MemoryBuffer deserializer(kerningTable, (unsigned)kerningTableSize);
+
+        unsigned short version = deserializer.ReadUShort();
+        if (version == 0)
+        {
+            unsigned numKerningTables = deserializer.ReadUShort();
+            for (unsigned i = 0; i < numKerningTables; ++i)
+            {
+                unsigned short version = deserializer.ReadUShort();
+                unsigned short length = deserializer.ReadUShort();
+                unsigned short coverage = deserializer.ReadUShort();
+
+                if (version == 0 && coverage == 1)
+                {
+                    unsigned numKerningPairs = deserializer.ReadUShort();
+                    // Skip searchRange, entrySelector and rangeShift
+                    deserializer.Seek((unsigned)(deserializer.GetPosition() + 3 * sizeof(unsigned short)));
+
+                    for (unsigned j = 0; j < numKerningPairs; ++j)
+                    {
+                        unsigned leftIndex = deserializer.ReadUShort();
+                        unsigned rightIndex = deserializer.ReadUShort();
+                        short amount = deserializer.ReadShort() >> 6;
+
+                        unsigned leftCharCode = leftIndex < numGlyphs ? charCodes[leftIndex] : 0;
+                        unsigned rightCharCode = rightIndex < numGlyphs ? charCodes[rightIndex] : 0;
+                        if (leftCharCode != 0 && rightCharCode != 0)
+                        {
+                            unsigned value = (leftCharCode << 16) + rightCharCode;
+                            kerningMapping_[value] = amount;
+                        }
+                    }
+                }
+                else
+                {
+                    // Kerning table contains information we do not support; skip and move to the next (length includes header)
+                    deserializer.Seek((unsigned)(deserializer.GetPosition() + length - 3 * sizeof(unsigned short)));
+                }
+            }
+        }
+        else
+            LOGWARNING("Can not read kerning information: not version 0");
+    }
+
+    if (loadAllGlyphs)
+    {
+        FT_Done_Face(face);
+        face_ = 0;
+        hasMutableGlyph_ = false;
+    }
+    else
+        hasMutableGlyph_ = true;
+
+    return true;
+}
+
+const FontGlyph* FontFaceFreeType::GetGlyph(unsigned c)
+{
+    HashMap<unsigned, FontGlyph>::Iterator i = glyphMapping_.Find(c);
+    if (i != glyphMapping_.End())
+    {
+        FontGlyph& glyph = i->second_;
+        glyph.used_ = true;
+        return &glyph;
+    }
+
+    if (LoadCharGlyph(c))
+    {
+        HashMap<unsigned, FontGlyph>::Iterator i = glyphMapping_.Find(c);
+        if (i != glyphMapping_.End())
+        {
+            FontGlyph& glyph = i->second_;
+            glyph.used_ = true;
+            return &glyph;
+        }
+    }
+
+    return 0;
+}
+
+bool FontFaceFreeType::CanLoadAllGlyphs(const PODVector<unsigned>& charCodes, int& textureWidth, int& textureHeight) const
+{
+    FT_Face face = (FT_Face)face_;
+    FT_GlyphSlot slot = face->glyph;
+    AreaAllocator allocator(FONT_TEXTURE_MIN_SIZE, FONT_TEXTURE_MIN_SIZE, textureWidth, textureHeight);
+
+    unsigned numGlyphs = charCodes.Size();
+    for (unsigned i = 0; i < numGlyphs; ++i)
+    {
+        unsigned charCode = charCodes[i];
+        if (charCode == 0)
+            continue;
+
+        FT_Error error = FT_Load_Char(face, charCode, loadMode_);
+        if (!error)
+        {
+            int width = (int)Max(slot->metrics.width >> 6, slot->bitmap.width);
+            int height = (int)Max(slot->metrics.height >> 6, slot->bitmap.rows);
+            int x, y;
+            if (!allocator.Allocate(width + 1, height + 1, x, y))
+                return false;
+        }
+    }
+
+    textureWidth = allocator.GetWidth();
+    textureHeight = allocator.GetHeight();
+    return true;
+}
+
+bool FontFaceFreeType::SetupNextTexture(int textureWidth, int textureHeight)
+{
+    SharedPtr<Image> image(new Image(font_->GetContext()));
+    image->SetSize(textureWidth, textureHeight, 1);
+    unsigned char* imageData = image->GetData();
+    memset(imageData, 0, (size_t)(image->GetWidth() * image->GetHeight()));
+
+    SharedPtr<Texture2D> texture = LoadFaceTexture(image);
+    if (!texture)
+        return false;
+
+    textures_.Push(texture);
+    allocator_.Reset(FONT_TEXTURE_MIN_SIZE, FONT_TEXTURE_MIN_SIZE, textureWidth, textureHeight);
+
+    font_->SetMemoryUse(font_->GetMemoryUse() + textureWidth * textureHeight);
+
+    return true;
+}
+
+bool FontFaceFreeType::LoadCharGlyph(unsigned charCode, Image* image)
+{
+    if (!face_)
+        return false;
+
+    FT_Face face = (FT_Face)face_;
+    FT_GlyphSlot slot = face->glyph;
+
+    FontGlyph fontGlyph;
+    FT_Error error = FT_Load_Char(face, charCode, loadMode_);
+    if (!error)
+    {
+        // Note: position within texture will be filled later
+        fontGlyph.width_ = (short)Max(slot->metrics.width >> 6, slot->bitmap.width);
+        fontGlyph.height_ = (short)Max(slot->metrics.height >> 6, slot->bitmap.rows);
+        fontGlyph.offsetX_ = (short)(slot->metrics.horiBearingX >> 6);
+        fontGlyph.offsetY_ = (short)(ascender_ - (slot->metrics.horiBearingY >> 6));
+        fontGlyph.advanceX_ = (short)(slot->metrics.horiAdvance >> 6);
+
+        if (fontGlyph.width_ > 0 && fontGlyph.height_ > 0)
+        {
+            int x, y;
+            if (!allocator_.Allocate(fontGlyph.width_ + 1, fontGlyph.height_ + 1, x, y))
+            {
+                if (!SetupNextTexture(allocator_.GetWidth(), allocator_.GetHeight()))
+                    return false;
+
+                if (!allocator_.Allocate(fontGlyph.width_ + 1, fontGlyph.height_ + 1, x, y))
+                    return false;
+            }
+
+            fontGlyph.x_ = (short)x;
+            fontGlyph.y_ = (short)y;
+
+            unsigned char* dest = 0;
+            unsigned pitch = 0;
+            if (image)
+            {
+                fontGlyph.page_ = 0;
+                dest = image->GetData() + fontGlyph.y_ * image->GetWidth() + fontGlyph.x_;
+                pitch = (unsigned)image->GetWidth();
+            }
+            else
+            {
+                fontGlyph.page_ = textures_.Size() - 1;
+                dest = new unsigned char[fontGlyph.width_ * fontGlyph.height_];
+                pitch = (unsigned)fontGlyph.width_;
+            }
+
+            FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL);
+            if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO)
+            {
+                for (int y = 0; y < slot->bitmap.rows; ++y)
+                {
+                    unsigned char* src = slot->bitmap.buffer + slot->bitmap.pitch * y;
+                    unsigned char* rowDest = dest + y * pitch;
+
+                    for (int x = 0; x < slot->bitmap.width; ++x)
+                        rowDest[x] = (unsigned char)((src[x >> 3] & (0x80 >> (x & 7))) ? 255 : 0);
+                }
+            }
+            else
+            {
+                for (int y = 0; y < slot->bitmap.rows; ++y)
+                {
+                    unsigned char* src = slot->bitmap.buffer + slot->bitmap.pitch * y;
+                    unsigned char* rowDest = dest + y * pitch;
+
+                    for (int x = 0; x < slot->bitmap.width; ++x)
+                        rowDest[x] = src[x];
+                }
+            }
+
+            if (!image)
+            {
+                textures_.Back()->SetData(0, fontGlyph.x_, fontGlyph.y_, fontGlyph.width_, fontGlyph.height_, dest);
+                delete[] dest;
+            }
+        }
+        else
+        {
+            fontGlyph.x_ = 0;
+            fontGlyph.y_ = 0;
+            fontGlyph.page_ = 0;
+        }
+    }
+    else
+    {
+        fontGlyph.width_ = 0;
+        fontGlyph.height_ = 0;
+        fontGlyph.offsetX_ = 0;
+        fontGlyph.offsetY_ = 0;
+        fontGlyph.advanceX_ = 0;
+        fontGlyph.page_ = 0;
+    }
+
+    glyphMapping_[charCode] = fontGlyph;
+
+    return true;
+}
+
+}
+
+}

+ 78 - 0
Source/Atomic/UI/SystemUI/FontFaceFreeType.h

@@ -0,0 +1,78 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "FontFace.h"
+
+namespace Atomic
+{
+
+class Texture2D;
+
+namespace SystemUI
+{
+
+class FreeTypeLibrary;
+
+/// Free type font face description.
+class ATOMIC_API FontFaceFreeType : public FontFace
+{
+public:
+    /// Construct.
+    FontFaceFreeType(Font* font);
+    /// Destruct.
+    ~FontFaceFreeType();
+
+    /// Load font face.
+    virtual bool Load(const unsigned char* fontData, unsigned fontDataSize, int pointSize);
+    /// Return pointer to the glyph structure corresponding to a character. Return null if glyph not found.
+    virtual const FontGlyph* GetGlyph(unsigned c);
+
+    /// Return if font face uses mutable glyphs.
+    virtual bool HasMutableGlyphs() const { return hasMutableGlyph_; }
+
+private:
+    /// Check can load all glyph in one texture, return true and texture size if can load.
+    bool CanLoadAllGlyphs(const PODVector<unsigned>& charCodes, int& textureWidth, int& textureHeight) const;
+    /// Setup next texture.
+    bool SetupNextTexture(int textureWidth, int textureHeight);
+    /// Load char glyph.
+    bool LoadCharGlyph(unsigned charCode, Image* image = 0);
+
+    /// FreeType library.
+    SharedPtr<FreeTypeLibrary> freeType_;
+    /// FreeType face. Non-null after creation only in dynamic mode.
+    void* face_;
+    /// Load mode.
+    int loadMode_;
+    /// Ascender.
+    int ascender_;
+    /// Has mutable glyph.
+    bool hasMutableGlyph_;
+    /// Glyph area allocator.
+    AreaAllocator allocator_;
+};
+
+}
+
+}

+ 669 - 0
Source/Atomic/UI/SystemUI/LineEdit.cpp

@@ -0,0 +1,669 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Input/Input.h"
+#include "LineEdit.h"
+#include "Text.h"
+#include "SystemUI.h"
+#include "SystemUIEvents.h"
+
+#include "../../DebugNew.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+
+StringHash VAR_DRAGDROPCONTENT("DragDropContent");
+
+extern const char* UI_CATEGORY;
+
+LineEdit::LineEdit(Context* context) :
+    BorderImage(context),
+    lastFont_(0),
+    lastFontSize_(0),
+    cursorPosition_(0),
+    dragBeginCursor_(M_MAX_UNSIGNED),
+    cursorBlinkRate_(1.0f),
+    cursorBlinkTimer_(0.0f),
+    maxLength_(0),
+    echoCharacter_(0),
+    cursorMovable_(true),
+    textSelectable_(true),
+    textCopyable_(true)
+{
+    clipChildren_ = true;
+    SetEnabled(true);
+    focusMode_ = FM_FOCUSABLE_DEFOCUSABLE;
+
+    text_ = CreateChild<Text>("LE_Text");
+    text_->SetInternal(true);
+    cursor_ = CreateChild<BorderImage>("LE_Cursor");
+    cursor_->SetInternal(true);
+    cursor_->SetPriority(1); // Show over text
+
+    SubscribeToEvent(this, E_FOCUSED, HANDLER(LineEdit, HandleFocused));
+    SubscribeToEvent(this, E_DEFOCUSED, HANDLER(LineEdit, HandleDefocused));
+    SubscribeToEvent(this, E_LAYOUTUPDATED, HANDLER(LineEdit, HandleLayoutUpdated));
+}
+
+LineEdit::~LineEdit()
+{
+}
+
+void LineEdit::RegisterObject(Context* context)
+{
+    context->RegisterFactory<LineEdit>(UI_CATEGORY);
+
+    COPY_BASE_ATTRIBUTES(BorderImage);
+    UPDATE_ATTRIBUTE_DEFAULT_VALUE("Clip Children", true);
+    UPDATE_ATTRIBUTE_DEFAULT_VALUE("Is Enabled", true);
+    UPDATE_ATTRIBUTE_DEFAULT_VALUE("Focus Mode", FM_FOCUSABLE_DEFOCUSABLE);
+    ACCESSOR_ATTRIBUTE("Max Length", GetMaxLength, SetMaxLength, unsigned, 0, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Is Cursor Movable", IsCursorMovable, SetCursorMovable, bool, true, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Is Text Selectable", IsTextSelectable, SetTextSelectable, bool, true, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Is Text Copyable", IsTextCopyable, SetTextCopyable, bool, true, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Cursor Blink Rate", GetCursorBlinkRate, SetCursorBlinkRate, float, 1.0f, AM_FILE);
+    ATTRIBUTE("Echo Character", int, echoCharacter_, 0, AM_FILE);
+}
+
+void LineEdit::ApplyAttributes()
+{
+    BorderImage::ApplyAttributes();
+
+    // Set the text's position to match clipping and indent width, so that text left edge is not left partially hidden
+    text_->SetPosition(GetIndentWidth() + clipBorder_.left_, clipBorder_.top_);
+
+    // Sync the text line
+    line_ = text_->GetText();
+}
+
+void LineEdit::Update(float timeStep)
+{
+    if (cursorBlinkRate_ > 0.0f)
+        cursorBlinkTimer_ = fmodf(cursorBlinkTimer_ + cursorBlinkRate_ * timeStep, 1.0f);
+
+    // Update cursor position if font has changed
+    if (text_->GetFont() != lastFont_ || text_->GetFontSize() != lastFontSize_)
+    {
+        lastFont_ = text_->GetFont();
+        lastFontSize_ = text_->GetFontSize();
+        UpdateCursor();
+    }
+
+    bool cursorVisible = HasFocus() ? cursorBlinkTimer_ < 0.5f : false;
+    cursor_->SetVisible(cursorVisible);
+}
+
+void LineEdit::OnClickBegin(const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers,
+    Cursor* cursor)
+{
+    if (button == MOUSEB_LEFT && cursorMovable_)
+    {
+        unsigned pos = GetCharIndex(position);
+        if (pos != M_MAX_UNSIGNED)
+        {
+            SetCursorPosition(pos);
+            text_->ClearSelection();
+        }
+    }
+}
+
+void LineEdit::OnDoubleClick(const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers,
+    Cursor* cursor)
+{
+    if (button == MOUSEB_LEFT)
+        text_->SetSelection(0);
+}
+
+void LineEdit::OnDragBegin(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers,
+    Cursor* cursor)
+{
+    UIElement::OnDragBegin(position, screenPosition, buttons, qualifiers, cursor);
+
+    dragBeginCursor_ = GetCharIndex(position);
+}
+
+void LineEdit::OnDragMove(const IntVector2& position, const IntVector2& screenPosition, const IntVector2& deltaPos, int buttons,
+    int qualifiers, Cursor* cursor)
+{
+    if (cursorMovable_ && textSelectable_)
+    {
+        unsigned start = dragBeginCursor_;
+        unsigned current = GetCharIndex(position);
+        if (start != M_MAX_UNSIGNED && current != M_MAX_UNSIGNED)
+        {
+            if (start < current)
+                text_->SetSelection(start, current - start);
+            else
+                text_->SetSelection(current, start - current);
+            SetCursorPosition(current);
+        }
+    }
+}
+
+bool LineEdit::OnDragDropTest(UIElement* source)
+{
+    if (source && editable_)
+    {
+        if (source->GetVars().Contains(VAR_DRAGDROPCONTENT))
+            return true;
+        StringHash sourceType = source->GetType();
+        return sourceType == LineEdit::GetTypeStatic() || sourceType == Text::GetTypeStatic();
+    }
+
+    return false;
+}
+
+bool LineEdit::OnDragDropFinish(UIElement* source)
+{
+    if (source && editable_)
+    {
+        // If the UI element in question has a drag-and-drop content string defined, use it instead of element text
+        if (source->GetVars().Contains(VAR_DRAGDROPCONTENT))
+        {
+            SetText(source->GetVar(VAR_DRAGDROPCONTENT).GetString());
+            return true;
+        }
+
+        StringHash sourceType = source->GetType();
+        if (sourceType == LineEdit::GetTypeStatic())
+        {
+            LineEdit* sourceLineEdit = static_cast<LineEdit*>(source);
+            SetText(sourceLineEdit->GetText());
+            return true;
+        }
+        else if (sourceType == Text::GetTypeStatic())
+        {
+            Text* sourceText = static_cast<Text*>(source);
+            SetText(sourceText->GetText());
+            return true;
+        }
+    }
+
+    return false;
+}
+
+void LineEdit::OnKey(int key, int buttons, int qualifiers)
+{
+    bool changed = false;
+    bool cursorMoved = false;
+
+    switch (key)
+    {
+    case 'X':
+    case 'C':
+        if (textCopyable_ && qualifiers & QUAL_CTRL)
+        {
+            unsigned start = text_->GetSelectionStart();
+            unsigned length = text_->GetSelectionLength();
+
+            if (text_->GetSelectionLength())
+                GetSubsystem<SystemUI>()->SetClipboardText(line_.SubstringUTF8(start, length));
+
+            if (key == 'X' && editable_)
+            {
+                if (start + length < line_.LengthUTF8())
+                    line_ = line_.SubstringUTF8(0, start) + line_.SubstringUTF8(start + length);
+                else
+                    line_ = line_.SubstringUTF8(0, start);
+                text_->ClearSelection();
+                cursorPosition_ = start;
+                changed = true;
+            }
+        }
+        break;
+
+    case 'V':
+        if (editable_ && textCopyable_ && qualifiers & QUAL_CTRL)
+        {
+            const String& clipBoard = GetSubsystem<SystemUI>()->GetClipboardText();
+            if (!clipBoard.Empty())
+            {
+                // Remove selected text first
+                if (text_->GetSelectionLength() > 0)
+                {
+                    unsigned start = text_->GetSelectionStart();
+                    unsigned length = text_->GetSelectionLength();
+                    if (start + length < line_.LengthUTF8())
+                        line_ = line_.SubstringUTF8(0, start) + line_.SubstringUTF8(start + length);
+                    else
+                        line_ = line_.SubstringUTF8(0, start);
+                    text_->ClearSelection();
+                    cursorPosition_ = start;
+                }
+                if (cursorPosition_ < line_.LengthUTF8())
+                    line_ = line_.SubstringUTF8(0, cursorPosition_) + clipBoard + line_.SubstringUTF8(cursorPosition_);
+                else
+                    line_ += clipBoard;
+                cursorPosition_ += clipBoard.LengthUTF8();
+                changed = true;
+            }
+        }
+        break;
+
+    case KEY_HOME:
+        qualifiers |= QUAL_CTRL;
+        // Fallthru
+
+    case KEY_LEFT:
+        if (cursorMovable_ && cursorPosition_ > 0)
+        {
+            if (textSelectable_ && qualifiers & QUAL_SHIFT && !text_->GetSelectionLength())
+                dragBeginCursor_ = cursorPosition_;
+
+            if (qualifiers & QUAL_CTRL)
+                cursorPosition_ = 0;
+            else if (text_->GetSelectionLength() && !(qualifiers & QUAL_SHIFT))
+                cursorPosition_ = text_->GetSelectionStart();
+            else
+                --cursorPosition_;
+            cursorMoved = true;
+
+            if (textSelectable_ && qualifiers & QUAL_SHIFT)
+            {
+                unsigned start = dragBeginCursor_;
+                unsigned current = cursorPosition_;
+                if (start < current)
+                    text_->SetSelection(start, current - start);
+                else
+                    text_->SetSelection(current, start - current);
+            }
+        }
+        if (!(qualifiers & QUAL_SHIFT))
+            text_->ClearSelection();
+        break;
+
+    case KEY_END:
+        qualifiers |= QUAL_CTRL;
+        // Fallthru
+
+    case KEY_RIGHT:
+        if (cursorMovable_ && cursorPosition_ < line_.LengthUTF8())
+        {
+            if (textSelectable_ && qualifiers & QUAL_SHIFT && !text_->GetSelectionLength())
+                dragBeginCursor_ = cursorPosition_;
+
+            if (qualifiers & QUAL_CTRL)
+                cursorPosition_ = line_.LengthUTF8();
+            else if (text_->GetSelectionLength() && !(qualifiers & QUAL_SHIFT))
+                cursorPosition_ = text_->GetSelectionStart() + text_->GetSelectionLength();
+            else
+                ++cursorPosition_;
+            cursorMoved = true;
+
+            if (textSelectable_ && qualifiers & QUAL_SHIFT)
+            {
+                unsigned start = dragBeginCursor_;
+                unsigned current = cursorPosition_;
+                if (start < current)
+                    text_->SetSelection(start, current - start);
+                else
+                    text_->SetSelection(current, start - current);
+            }
+        }
+        if (!(qualifiers & QUAL_SHIFT))
+            text_->ClearSelection();
+        break;
+
+    case KEY_DELETE:
+        if (editable_)
+        {
+            if (!text_->GetSelectionLength())
+            {
+                if (cursorPosition_ < line_.LengthUTF8())
+                {
+                    line_ = line_.SubstringUTF8(0, cursorPosition_) + line_.SubstringUTF8(cursorPosition_ + 1);
+                    changed = true;
+                }
+            }
+            else
+            {
+                // If a selection exists, erase it
+                unsigned start = text_->GetSelectionStart();
+                unsigned length = text_->GetSelectionLength();
+                if (start + length < line_.LengthUTF8())
+                    line_ = line_.SubstringUTF8(0, start) + line_.SubstringUTF8(start + length);
+                else
+                    line_ = line_.SubstringUTF8(0, start);
+                text_->ClearSelection();
+                cursorPosition_ = start;
+                changed = true;
+            }
+        }
+        break;
+
+    case KEY_UP:
+    case KEY_DOWN:
+    case KEY_PAGEUP:
+    case KEY_PAGEDOWN:
+        {
+            using namespace UnhandledKey;
+
+            VariantMap& eventData = GetEventDataMap();
+            eventData[P_ELEMENT] = this;
+            eventData[P_KEY] = key;
+            eventData[P_BUTTONS] = buttons;
+            eventData[P_QUALIFIERS] = qualifiers;
+            SendEvent(E_UNHANDLEDKEY, eventData);
+        }
+        return;
+
+    case KEY_BACKSPACE:
+        if (editable_)
+        {
+            if (!text_->GetSelectionLength())
+            {
+                if (line_.LengthUTF8() && cursorPosition_)
+                {
+                    if (cursorPosition_ < line_.LengthUTF8())
+                        line_ = line_.SubstringUTF8(0, cursorPosition_ - 1) + line_.SubstringUTF8(cursorPosition_);
+                    else
+                        line_ = line_.SubstringUTF8(0, cursorPosition_ - 1);
+                    --cursorPosition_;
+                    changed = true;
+                }
+            }
+            else
+            {
+                // If a selection exists, erase it
+                unsigned start = text_->GetSelectionStart();
+                unsigned length = text_->GetSelectionLength();
+                if (start + length < line_.LengthUTF8())
+                    line_ = line_.SubstringUTF8(0, start) + line_.SubstringUTF8(start + length);
+                else
+                    line_ = line_.SubstringUTF8(0, start);
+                text_->ClearSelection();
+                cursorPosition_ = start;
+                changed = true;
+            }
+        }
+        break;
+
+    case KEY_RETURN:
+    case KEY_RETURN2:
+    case KEY_KP_ENTER:
+        {
+            // If using the on-screen keyboard, defocus this element to hide it now
+            if (GetSubsystem<SystemUI>()->GetUseScreenKeyboard() && HasFocus())
+                SetFocus(false);
+
+            using namespace TextFinished;
+
+            VariantMap& eventData = GetEventDataMap();
+            eventData[P_ELEMENT] = this;
+            eventData[P_TEXT] = line_;
+            SendEvent(E_TEXTFINISHED, eventData);
+            return;
+        }
+
+    default: break;
+    }
+
+    if (changed)
+    {
+        UpdateText();
+        UpdateCursor();
+    }
+    else if (cursorMoved)
+        UpdateCursor();
+}
+
+void LineEdit::OnTextInput(const String& text, int buttons, int qualifiers)
+{
+    if (!editable_)
+        return;
+
+    bool changed = false;
+
+    // If only CTRL is held down, do not edit
+    if ((qualifiers & (QUAL_CTRL | QUAL_ALT)) == QUAL_CTRL)
+        return;
+
+    // Send char as an event to allow changing it
+    using namespace CharEntry;
+
+    VariantMap& eventData = GetEventDataMap();
+    eventData[P_ELEMENT] = this;
+    eventData[P_TEXT] = text;
+    eventData[P_BUTTONS] = buttons;
+    eventData[P_QUALIFIERS] = qualifiers;
+    SendEvent(E_TEXTENTRY, eventData);
+
+    const String newText = eventData[P_TEXT].GetString().SubstringUTF8(0);
+    if (!newText.Empty() && (!maxLength_ || line_.LengthUTF8() + newText.LengthUTF8() <= maxLength_))
+    {
+        if (!text_->GetSelectionLength())
+        {
+            if (cursorPosition_ == line_.LengthUTF8())
+                line_ += newText;
+            else
+                line_ = line_.SubstringUTF8(0, cursorPosition_) + newText + line_.SubstringUTF8(cursorPosition_);
+            cursorPosition_ += newText.LengthUTF8();
+        }
+        else
+        {
+            // If a selection exists, erase it first
+            unsigned start = text_->GetSelectionStart();
+            unsigned length = text_->GetSelectionLength();
+            if (start + length < line_.LengthUTF8())
+                line_ = line_.SubstringUTF8(0, start) + newText + line_.SubstringUTF8(start + length);
+            else
+                line_ = line_.SubstringUTF8(0, start) + newText;
+            cursorPosition_ = start + newText.LengthUTF8();
+        }
+        changed = true;
+    }
+
+    if (changed)
+    {
+        text_->ClearSelection();
+        UpdateText();
+        UpdateCursor();
+    }
+}
+
+void LineEdit::SetText(const String& text)
+{
+    if (text != line_)
+    {
+        line_ = text;
+        cursorPosition_ = line_.LengthUTF8();
+        UpdateText();
+        UpdateCursor();
+    }
+}
+
+void LineEdit::SetCursorPosition(unsigned position)
+{
+    if (position > line_.LengthUTF8() || !cursorMovable_)
+        position = line_.LengthUTF8();
+
+    if (position != cursorPosition_)
+    {
+        cursorPosition_ = position;
+        UpdateCursor();
+    }
+}
+
+void LineEdit::SetCursorBlinkRate(float rate)
+{
+    cursorBlinkRate_ = Max(rate, 0.0f);
+
+    if (cursorBlinkRate_ == 0.0f)
+        cursorBlinkTimer_ = 0.0f;   // Cursor does not blink, i.e. always visible
+}
+
+void LineEdit::SetMaxLength(unsigned length)
+{
+    maxLength_ = length;
+}
+
+void LineEdit::SetEchoCharacter(unsigned c)
+{
+    echoCharacter_ = c;
+    UpdateText();
+}
+
+void LineEdit::SetCursorMovable(bool enable)
+{
+    cursorMovable_ = enable;
+}
+
+void LineEdit::SetTextSelectable(bool enable)
+{
+    textSelectable_ = enable;
+}
+
+void LineEdit::SetTextCopyable(bool enable)
+{
+    textCopyable_ = enable;
+}
+
+bool LineEdit::FilterImplicitAttributes(XMLElement& dest) const
+{
+    if (!BorderImage::FilterImplicitAttributes(dest))
+        return false;
+
+    XMLElement childElem = dest.GetChild("element");
+    if (!childElem)
+        return false;
+    if (!RemoveChildXML(childElem, "Name", "LE_Text"))
+        return false;
+    if (!RemoveChildXML(childElem, "Position"))
+        return false;
+
+    childElem = childElem.GetNext("element");
+    if (!childElem)
+        return false;
+    if (!RemoveChildXML(childElem, "Name", "LE_Cursor"))
+        return false;
+    if (!RemoveChildXML(childElem, "Priority", "1"))
+        return false;
+    if (!RemoveChildXML(childElem, "Position"))
+        return false;
+    if (!RemoveChildXML(childElem, "Is Visible"))
+        return false;
+
+    return true;
+}
+
+void LineEdit::UpdateText()
+{
+    unsigned utf8Length = line_.LengthUTF8();
+
+    if (!echoCharacter_)
+        text_->SetText(line_);
+    else
+    {
+        String echoText;
+        for (unsigned i = 0; i < utf8Length; ++i)
+            echoText.AppendUTF8(echoCharacter_);
+        text_->SetText(echoText);
+    }
+    if (cursorPosition_ > utf8Length)
+    {
+        cursorPosition_ = utf8Length;
+        UpdateCursor();
+    }
+
+    using namespace TextChanged;
+
+    VariantMap& eventData = GetEventDataMap();
+    eventData[P_ELEMENT] = this;
+    eventData[P_TEXT] = line_;
+    SendEvent(E_TEXTCHANGED, eventData);
+}
+
+void LineEdit::UpdateCursor()
+{
+    int x = text_->GetCharPosition(cursorPosition_).x_;
+
+    text_->SetPosition(GetIndentWidth() + clipBorder_.left_, clipBorder_.top_);
+    cursor_->SetPosition(text_->GetPosition() + IntVector2(x, 0));
+    cursor_->SetSize(cursor_->GetWidth(), text_->GetRowHeight());
+
+    // Scroll if necessary
+    int sx = -GetChildOffset().x_;
+    int left = clipBorder_.left_;
+    int right = GetWidth() - clipBorder_.left_ - clipBorder_.right_ - cursor_->GetWidth();
+    if (x - sx > right)
+        sx = x - right;
+    if (x - sx < left)
+        sx = x - left;
+    if (sx < 0)
+        sx = 0;
+    SetChildOffset(IntVector2(-sx, 0));
+
+    // Restart blinking
+    cursorBlinkTimer_ = 0.0f;
+}
+
+unsigned LineEdit::GetCharIndex(const IntVector2& position)
+{
+    IntVector2 screenPosition = ElementToScreen(position);
+    IntVector2 textPosition = text_->ScreenToElement(screenPosition);
+
+    if (textPosition.x_ < 0)
+        return 0;
+
+    for (int i = text_->GetNumChars(); i >= 0; --i)
+    {
+        if (textPosition.x_ >= text_->GetCharPosition((unsigned)i).x_)
+            return (unsigned)i;
+    }
+
+    return M_MAX_UNSIGNED;
+}
+
+void LineEdit::HandleFocused(StringHash eventType, VariantMap& eventData)
+{
+    if (eventData[Focused::P_BYKEY].GetBool())
+    {
+        cursorPosition_ = line_.LengthUTF8();
+        text_->SetSelection(0);
+    }
+    UpdateCursor();
+
+    if (GetSubsystem<SystemUI>()->GetUseScreenKeyboard())
+        GetSubsystem<Input>()->SetScreenKeyboardVisible(true);
+}
+
+void LineEdit::HandleDefocused(StringHash eventType, VariantMap& eventData)
+{
+    text_->ClearSelection();
+
+    if (GetSubsystem<SystemUI>()->GetUseScreenKeyboard())
+        GetSubsystem<Input>()->SetScreenKeyboardVisible(false);
+}
+
+void LineEdit::HandleLayoutUpdated(StringHash eventType, VariantMap& eventData)
+{
+    UpdateCursor();
+}
+
+}
+
+}

+ 172 - 0
Source/Atomic/UI/SystemUI/LineEdit.h

@@ -0,0 +1,172 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "BorderImage.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+class Font;
+class Text;
+
+/// Single-line text editor %UI element.
+class ATOMIC_API LineEdit : public BorderImage
+{
+    OBJECT(LineEdit);
+
+public:
+    /// Construct.
+    LineEdit(Context* context);
+    /// Destruct.
+    virtual ~LineEdit();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Apply attribute changes that can not be applied immediately.
+    virtual void ApplyAttributes();
+    /// Perform UI element update.
+    virtual void Update(float timeStep);
+    /// React to mouse click begin.
+    virtual void OnClickBegin
+        (const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers, Cursor* cursor);
+    /// React to mouse doubleclick.
+    virtual void OnDoubleClick
+        (const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers, Cursor* cursor);
+    /// React to mouse drag begin.
+    virtual void
+        OnDragBegin(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
+    /// React to mouse drag motion.
+    virtual void OnDragMove
+        (const IntVector2& position, const IntVector2& screenPosition, const IntVector2& deltaPos, int buttons, int qualifiers,
+            Cursor* cursor);
+    /// React to drag and drop test. Return true to signal that the drop is acceptable.
+    virtual bool OnDragDropTest(UIElement* source);
+    /// React to drag and drop finish. Return true to signal that the drop was accepted.
+    virtual bool OnDragDropFinish(UIElement* source);
+    /// React to a key press.
+    virtual void OnKey(int key, int buttons, int qualifiers);
+    /// React to text input event.
+    virtual void OnTextInput(const String& text, int buttons, int qualifiers);
+
+    /// Set text.
+    void SetText(const String& text);
+    /// Set cursor position.
+    void SetCursorPosition(unsigned position);
+    /// Set cursor blink rate. 0 disables blinking.
+    void SetCursorBlinkRate(float rate);
+    /// Set maximum text length. 0 for unlimited.
+    void SetMaxLength(unsigned length);
+    /// Set echo character for password entry and such. 0 (default) shows the actual text.
+    void SetEchoCharacter(unsigned c);
+    /// Set whether can move cursor with arrows or mouse, default true.
+    void SetCursorMovable(bool enable);
+    /// Set whether selections are allowed, default true.
+    void SetTextSelectable(bool enable);
+    /// Set whether copy-paste operations are allowed, default true.
+    void SetTextCopyable(bool enable);
+
+    /// Return text.
+    const String& GetText() const { return line_; }
+
+    /// Return cursor position.
+    unsigned GetCursorPosition() const { return cursorPosition_; }
+
+    /// Return cursor blink rate.
+    float GetCursorBlinkRate() const { return cursorBlinkRate_; }
+
+    /// Return maximum text length.
+    unsigned GetMaxLength() const { return maxLength_; }
+
+    /// Return echo character.
+    unsigned GetEchoCharacter() const { return echoCharacter_; }
+
+    /// Return whether can move cursor with arrows or mouse.
+    bool IsCursorMovable() const { return cursorMovable_; }
+
+    /// Return whether selections are allowed.
+    bool IsTextSelectable() const { return textSelectable_; }
+
+    /// Return whether copy-paste operations are allowed.
+    bool IsTextCopyable() const { return textCopyable_; }
+
+    /// Return text element.
+    Text* GetTextElement() const { return text_; }
+
+    /// Return cursor element.
+    BorderImage* GetCursor() const { return cursor_; }
+
+protected:
+    /// Filter implicit attributes in serialization process.
+    virtual bool FilterImplicitAttributes(XMLElement& dest) const;
+    /// Update displayed text.
+    void UpdateText();
+    /// Update cursor position and restart cursor blinking.
+    void UpdateCursor();
+    /// Return char index corresponding to position within element, or M_MAX_UNSIGNED if not found.
+    unsigned GetCharIndex(const IntVector2& position);
+
+    /// Text element.
+    SharedPtr<Text> text_;
+    /// Cursor element.
+    SharedPtr<BorderImage> cursor_;
+    /// Text line.
+    String line_;
+    /// Last used text font.
+    Font* lastFont_;
+    /// Last used text size.
+    int lastFontSize_;
+    /// Text edit cursor position.
+    unsigned cursorPosition_;
+    /// Drag begin cursor position.
+    unsigned dragBeginCursor_;
+    /// Cursor blink rate.
+    float cursorBlinkRate_;
+    /// Cursor blink timer.
+    float cursorBlinkTimer_;
+    /// Maximum text length.
+    unsigned maxLength_;
+    /// Echo character.
+    unsigned echoCharacter_;
+    /// Cursor movable flag.
+    bool cursorMovable_;
+    /// Text selectable flag.
+    bool textSelectable_;
+    /// Copy-paste enable flag.
+    bool textCopyable_;
+
+private:
+    /// Handle being focused.
+    void HandleFocused(StringHash eventType, VariantMap& eventData);
+    /// Handle being defocused.
+    void HandleDefocused(StringHash eventType, VariantMap& eventData);
+    /// Handle the element layout having been updated.
+    void HandleLayoutUpdated(StringHash eventType, VariantMap& eventData);
+};
+
+}
+
+}

+ 1144 - 0
Source/Atomic/UI/SystemUI/ListView.cpp

@@ -0,0 +1,1144 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Input/InputEvents.h"
+#include "../../IO/Log.h"
+#include "CheckBox.h"
+#include "ListView.h"
+#include "Text.h"
+#include "SystemUI.h"
+#include "SystemUIEvents.h"
+
+#include "../../DebugNew.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+
+static const char* highlightModes[] =
+{
+    "Never",
+    "Focus",
+    "Always",
+    0
+};
+
+static const StringHash expandedHash("Expanded");
+
+extern const char* UI_CATEGORY;
+
+bool GetItemExpanded(UIElement* item)
+{
+    return item ? item->GetVar(expandedHash).GetBool() : false;
+}
+
+void SetItemExpanded(UIElement* item, bool enable)
+{
+    item->SetVar(expandedHash, enable);
+}
+
+static const StringHash hierarchyParentHash("HierarchyParent");
+
+bool GetItemHierarchyParent(UIElement* item)
+{
+    return item ? item->GetVar(hierarchyParentHash).GetBool() : false;
+}
+
+void SetItemHierarchyParent(UIElement* item, bool enable)
+{
+    item->SetVar(hierarchyParentHash, enable);
+}
+
+/// Hierarchy container (used by ListView internally when in hierarchy mode).
+class HierarchyContainer : public UIElement
+{
+    OBJECT(HierarchyContainer);
+
+public:
+    /// Construct.
+    HierarchyContainer(Context* context, ListView* listView, UIElement* overlayContainer) :
+        UIElement(context),
+        listView_(listView),
+        overlayContainer_(overlayContainer)
+    {
+        SubscribeToEvent(this, E_LAYOUTUPDATED, HANDLER(HierarchyContainer, HandleLayoutUpdated));
+        SubscribeToEvent(overlayContainer->GetParent(), E_VIEWCHANGED, HANDLER(HierarchyContainer, HandleViewChanged));
+        SubscribeToEvent(E_UIMOUSECLICK, HANDLER(HierarchyContainer, HandleUIMouseClick));
+    }
+
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Handle layout updated by adjusting the position of the overlays.
+    void HandleLayoutUpdated(StringHash eventType, VariantMap& eventData)
+    {
+        // Adjust the container size for child clipping effect
+        overlayContainer_->SetSize(GetParent()->GetSize());
+
+        for (unsigned i = 0; i < children_.Size(); ++i)
+        {
+            const IntVector2& position = children_[i]->GetPosition();
+            CheckBox* overlay = static_cast<CheckBox*>(overlayContainer_->GetChild(i));
+            bool visible = children_[i]->IsVisible() && GetItemHierarchyParent(children_[i]);
+            overlay->SetVisible(visible);
+            if (visible)
+            {
+                overlay->SetPosition(position.x_, position.y_);
+                overlay->SetChecked(GetItemExpanded(children_[i]));
+            }
+        }
+    }
+
+    /// Handle view changed by scrolling the overlays in tandem.
+    void HandleViewChanged(StringHash eventType, VariantMap& eventData)
+    {
+        using namespace ViewChanged;
+
+        int x = eventData[P_X].GetInt();
+        int y = eventData[P_Y].GetInt();
+
+        IntRect panelBorder = GetParent()->GetClipBorder();
+        overlayContainer_->SetChildOffset(IntVector2(-x + panelBorder.left_, -y + panelBorder.top_));
+    }
+
+    /// Handle mouse click on overlays by toggling the expansion state of the corresponding item
+    void HandleUIMouseClick(StringHash eventType, VariantMap& eventData)
+    {
+        using namespace UIMouseClick;
+
+        UIElement* overlay = static_cast<UIElement*>(eventData[UIMouseClick::P_ELEMENT].GetPtr());
+        if (overlay)
+        {
+            const Vector<SharedPtr<UIElement> >& children = overlayContainer_->GetChildren();
+            Vector<SharedPtr<UIElement> >::ConstIterator i = children.Find(SharedPtr<UIElement>(overlay));
+            if (i != children.End())
+                listView_->ToggleExpand((unsigned)(i - children.Begin()));
+        }
+    }
+
+    /// Insert a child element into a specific position in the child list.
+    void InsertChild(unsigned index, UIElement* element)
+    {
+        // Insert the overlay at the same index position to the overlay container
+        CheckBox* overlay = static_cast<CheckBox*>(overlayContainer_->CreateChild(CheckBox::GetTypeStatic(), String::EMPTY, index));
+        overlay->SetStyle("HierarchyListViewOverlay");
+        int baseIndent = listView_->GetBaseIndent();
+        int indent = element->GetIndent() - baseIndent - 1;
+        overlay->SetIndent(indent);
+        overlay->SetFixedWidth((indent + 1) * element->GetIndentSpacing());
+
+        // Then insert the element as child as per normal
+        UIElement::InsertChild(index, element);
+    }
+
+private:
+    // Parent list view.
+    ListView* listView_;
+    // Container for overlay checkboxes.
+    UIElement* overlayContainer_;
+};
+
+void HierarchyContainer::RegisterObject(Context* context)
+{
+    COPY_BASE_ATTRIBUTES(UIElement);
+}
+
+ListView::ListView(Context* context) :
+    ScrollView(context),
+    highlightMode_(HM_FOCUS),
+    multiselect_(false),
+    hierarchyMode_(true),    // Init to true here so that the setter below takes effect
+    baseIndent_(0),
+    clearSelectionOnDefocus_(false),
+    selectOnClickEnd_(false)
+{
+    resizeContentWidth_ = true;
+
+    // By default list view is set to non-hierarchy mode
+    SetHierarchyMode(false);
+
+    SubscribeToEvent(E_UIMOUSEDOUBLECLICK, HANDLER(ListView, HandleUIMouseDoubleClick));
+    SubscribeToEvent(E_FOCUSCHANGED, HANDLER(ListView, HandleItemFocusChanged));
+    SubscribeToEvent(this, E_DEFOCUSED, HANDLER(ListView, HandleFocusChanged));
+    SubscribeToEvent(this, E_FOCUSED, HANDLER(ListView, HandleFocusChanged));
+
+    UpdateUIClickSubscription();
+}
+
+ListView::~ListView()
+{
+}
+
+void ListView::RegisterObject(Context* context)
+{
+    context->RegisterFactory<ListView>(UI_CATEGORY);
+
+    HierarchyContainer::RegisterObject(context);
+
+    COPY_BASE_ATTRIBUTES(ScrollView);
+    ENUM_ACCESSOR_ATTRIBUTE("Highlight Mode", GetHighlightMode, SetHighlightMode, HighlightMode, highlightModes, HM_FOCUS, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Multiselect", GetMultiselect, SetMultiselect, bool, false, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Hierarchy Mode", GetHierarchyMode, SetHierarchyMode, bool, false, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Base Indent", GetBaseIndent, SetBaseIndent, int, 0, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Clear Sel. On Defocus", GetClearSelectionOnDefocus, SetClearSelectionOnDefocus, bool, false, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Select On Click End", GetSelectOnClickEnd, SetSelectOnClickEnd, bool, false, AM_FILE);
+}
+
+void ListView::OnKey(int key, int buttons, int qualifiers)
+{
+    // If no selection, can not move with keys
+    unsigned numItems = GetNumItems();
+    unsigned selection = GetSelection();
+
+    // If either shift or ctrl held down, add to selection if multiselect enabled
+    bool additive = multiselect_ && qualifiers & (QUAL_SHIFT | QUAL_CTRL);
+    int delta = M_MAX_INT;
+    int pageDirection = 1;
+
+    if (numItems)
+    {
+        if (selection != M_MAX_UNSIGNED && qualifiers & QUAL_CTRL && key == KEY_C)
+        {
+            CopySelectedItemsToClipboard();
+            return;
+        }
+
+        switch (key)
+        {
+        case KEY_LEFT:
+        case KEY_RIGHT:
+            if (selection != M_MAX_UNSIGNED && hierarchyMode_)
+            {
+                Expand(selection, key == KEY_RIGHT);
+                return;
+            }
+            break;
+
+        case KEY_RETURN:
+        case KEY_RETURN2:
+        case KEY_KP_ENTER:
+            if (selection != M_MAX_UNSIGNED && hierarchyMode_)
+            {
+                ToggleExpand(selection);
+                return;
+            }
+            break;
+
+        case KEY_UP:
+            delta = -1;
+            break;
+
+        case KEY_DOWN:
+            delta = 1;
+            break;
+
+        case KEY_PAGEUP:
+            pageDirection = -1;
+            // Fallthru
+
+        case KEY_PAGEDOWN:
+            {
+                // Convert page step to pixels and see how many items have to be skipped to reach that many pixels
+                if (selection == M_MAX_UNSIGNED)
+                    selection = 0;      // Assume as if first item is selected
+                int stepPixels = ((int)(pageStep_ * scrollPanel_->GetHeight())) - contentElement_->GetChild(selection)->GetHeight();
+                unsigned newSelection = selection;
+                unsigned okSelection = selection;
+                unsigned invisible = 0;
+                while (newSelection < numItems)
+                {
+                    UIElement* item = GetItem(newSelection);
+                    int height = 0;
+                    if (item->IsVisible())
+                    {
+                        height = item->GetHeight();
+                        okSelection = newSelection;
+                    }
+                    else
+                        ++invisible;
+                    if (stepPixels < height)
+                        break;
+                    stepPixels -= height;
+                    newSelection += pageDirection;
+                }
+                delta = okSelection - selection - pageDirection * invisible;
+            }
+            break;
+
+        case KEY_HOME:
+            delta = -(int)GetNumItems();
+            break;
+
+        case KEY_END:
+            delta = GetNumItems();
+            break;
+
+        default: break;
+        }
+    }
+
+    if (delta != M_MAX_INT)
+    {
+        ChangeSelection(delta, additive);
+        return;
+    }
+
+    using namespace UnhandledKey;
+
+    VariantMap& eventData = GetEventDataMap();
+    eventData[P_ELEMENT] = this;
+    eventData[P_KEY] = key;
+    eventData[P_BUTTONS] = buttons;
+    eventData[P_QUALIFIERS] = qualifiers;
+    SendEvent(E_UNHANDLEDKEY, eventData);
+}
+
+void ListView::OnResize()
+{
+    ScrollView::OnResize();
+
+    // When in hierarchy mode also need to resize the overlay container
+    if (hierarchyMode_)
+        overlayContainer_->SetSize(scrollPanel_->GetSize());
+}
+
+void ListView::AddItem(UIElement* item)
+{
+    InsertItem(M_MAX_UNSIGNED, item);
+}
+
+void ListView::InsertItem(unsigned index, UIElement* item, UIElement* parentItem)
+{
+    if (!item || item->GetParent() == contentElement_)
+        return;
+
+    // Enable input so that clicking the item can be detected
+    item->SetEnabled(true);
+    item->SetSelected(false);
+
+    unsigned numItems = contentElement_->GetNumChildren();
+    if (hierarchyMode_)
+    {
+        int baseIndent = baseIndent_;
+        if (parentItem)
+        {
+            baseIndent = parentItem->GetIndent();
+            SetItemHierarchyParent(parentItem, true);
+
+            // Adjust the index to ensure it is within the children index limit of the parent item
+            unsigned indexLimit = FindItem(parentItem);
+            if (index <= indexLimit)
+                index = indexLimit + 1;
+            else
+            {
+                while (++indexLimit < numItems)
+                {
+                    if (contentElement_->GetChild(indexLimit)->GetIndent() <= baseIndent)
+                        break;
+                }
+                if (index > indexLimit)
+                    index = indexLimit;
+            }
+        }
+        item->SetIndent(baseIndent + 1);
+        SetItemExpanded(item, item->IsVisible());
+
+        // Use the 'overrided' version to insert the child item
+        static_cast<HierarchyContainer*>(contentElement_.Get())->InsertChild(index, item);
+    }
+    else
+    {
+        if (index > numItems)
+            index = numItems;
+
+        contentElement_->InsertChild(index, item);
+    }
+
+    // If necessary, shift the following selections
+    if (!selections_.Empty())
+    {
+        for (unsigned i = 0; i < selections_.Size(); ++i)
+        {
+            if (selections_[i] >= index)
+                ++selections_[i];
+        }
+
+        UpdateSelectionEffect();
+    }
+}
+
+void ListView::RemoveItem(UIElement* item, unsigned index)
+{
+    if (!item)
+        return;
+
+    unsigned numItems = GetNumItems();
+    for (unsigned i = index; i < numItems; ++i)
+    {
+        if (GetItem(i) == item)
+        {
+            item->SetSelected(false);
+            selections_.Remove(i);
+
+            unsigned removed = 1;
+            if (hierarchyMode_)
+            {
+                // Remove any child items in hierarchy mode
+                if (GetItemHierarchyParent(item))
+                {
+                    int baseIndent = item->GetIndent();
+                    for (unsigned j = i + 1; ; ++j)
+                    {
+                        UIElement* childItem = GetItem(i + 1);
+                        if (!childItem)
+                            break;
+                        if (childItem->GetIndent() > baseIndent)
+                        {
+                            childItem->SetSelected(false);
+                            selections_.Erase(j);
+                            contentElement_->RemoveChildAtIndex(i + 1);
+                            overlayContainer_->RemoveChildAtIndex(i + 1);
+                            ++removed;
+                        }
+                        else
+                            break;
+                    }
+                }
+
+                // Check if the parent of removed item still has other children
+                if (i > 0)
+                {
+                    int baseIndent = item->GetIndent();
+                    UIElement* prevKin = GetItem(i - 1);        // Could be parent or sibling
+                    if (prevKin->GetIndent() < baseIndent)
+                    {
+                        UIElement* nextKin = GetItem(i + 1);    // Could be sibling or parent-sibling or 0 if index out of bound
+                        if (!nextKin || nextKin->GetIndent() < baseIndent)
+                        {
+                            // If we reach here then the parent has no other children
+                            SetItemHierarchyParent(prevKin, false);
+                        }
+                    }
+                }
+
+                // Remove the overlay at the same index
+                overlayContainer_->RemoveChildAtIndex(i);
+            }
+
+            // If necessary, shift the following selections
+            if (!selections_.Empty())
+            {
+                for (unsigned j = 0; j < selections_.Size(); ++j)
+                {
+                    if (selections_[j] > i)
+                        selections_[j] -= removed;
+                }
+
+                UpdateSelectionEffect();
+            }
+
+            contentElement_->RemoveChildAtIndex(i);
+            break;
+        }
+    }
+}
+
+void ListView::RemoveItem(unsigned index)
+{
+    RemoveItem(GetItem(index), index);
+}
+
+void ListView::RemoveAllItems()
+{
+    contentElement_->DisableLayoutUpdate();
+
+    ClearSelection();
+    contentElement_->RemoveAllChildren();
+    if (hierarchyMode_)
+        overlayContainer_->RemoveAllChildren();
+
+    contentElement_->EnableLayoutUpdate();
+    contentElement_->UpdateLayout();
+}
+
+void ListView::SetSelection(unsigned index)
+{
+    PODVector<unsigned> indices;
+    indices.Push(index);
+    SetSelections(indices);
+    EnsureItemVisibility(index);
+}
+
+void ListView::SetSelections(const PODVector<unsigned>& indices)
+{
+    // Make a weak pointer to self to check for destruction as a response to events
+    WeakPtr<ListView> self(this);
+
+    unsigned numItems = GetNumItems();
+
+    // Remove first items that should no longer be selected
+    for (PODVector<unsigned>::Iterator i = selections_.Begin(); i != selections_.End();)
+    {
+        unsigned index = *i;
+        if (!indices.Contains(index))
+        {
+            i = selections_.Erase(i);
+
+            using namespace ItemSelected;
+
+            VariantMap& eventData = GetEventDataMap();
+            eventData[P_ELEMENT] = this;
+            eventData[P_SELECTION] = index;
+            SendEvent(E_ITEMDESELECTED, eventData);
+
+            if (self.Expired())
+                return;
+        }
+        else
+            ++i;
+    }
+
+    bool added = false;
+
+    // Then add missing items
+    for (PODVector<unsigned>::ConstIterator i = indices.Begin(); i != indices.End(); ++i)
+    {
+        unsigned index = *i;
+        if (index < numItems)
+        {
+            // In singleselect mode, resend the event even for the same selection
+            bool duplicate = selections_.Contains(index);
+            if (!duplicate || !multiselect_)
+            {
+                if (!duplicate)
+                {
+                    selections_.Push(index);
+                    added = true;
+                }
+
+                using namespace ItemSelected;
+
+                VariantMap& eventData = GetEventDataMap();
+                eventData[P_ELEMENT] = this;
+                eventData[P_SELECTION] = *i;
+                SendEvent(E_ITEMSELECTED, eventData);
+
+                if (self.Expired())
+                    return;
+            }
+        }
+        // If no multiselect enabled, allow setting only one item
+        if (!multiselect_)
+            break;
+    }
+
+    // Re-sort selections if necessary
+    if (added)
+        Sort(selections_.Begin(), selections_.End());
+
+    UpdateSelectionEffect();
+    SendEvent(E_SELECTIONCHANGED);
+}
+
+void ListView::AddSelection(unsigned index)
+{
+    // Make a weak pointer to self to check for destruction as a response to events
+    WeakPtr<ListView> self(this);
+
+    if (!multiselect_)
+        SetSelection(index);
+    else
+    {
+        if (index >= GetNumItems())
+            return;
+
+        if (!selections_.Contains(index))
+        {
+            selections_.Push(index);
+
+            using namespace ItemSelected;
+
+            VariantMap& eventData = GetEventDataMap();
+            eventData[P_ELEMENT] = this;
+            eventData[P_SELECTION] = index;
+            SendEvent(E_ITEMSELECTED, eventData);
+
+            if (self.Expired())
+                return;
+
+            Sort(selections_.Begin(), selections_.End());
+        }
+
+        EnsureItemVisibility(index);
+        UpdateSelectionEffect();
+        SendEvent(E_SELECTIONCHANGED);
+    }
+}
+
+void ListView::RemoveSelection(unsigned index)
+{
+    if (index >= GetNumItems())
+        return;
+
+    if (selections_.Remove(index))
+    {
+        using namespace ItemSelected;
+
+        VariantMap& eventData = GetEventDataMap();
+        eventData[P_ELEMENT] = this;
+        eventData[P_SELECTION] = index;
+        SendEvent(E_ITEMDESELECTED, eventData);
+    }
+
+    EnsureItemVisibility(index);
+    UpdateSelectionEffect();
+    SendEvent(E_SELECTIONCHANGED);
+}
+
+void ListView::ToggleSelection(unsigned index)
+{
+    unsigned numItems = GetNumItems();
+    if (index >= numItems)
+        return;
+
+    if (selections_.Contains(index))
+        RemoveSelection(index);
+    else
+        AddSelection(index);
+}
+
+void ListView::ChangeSelection(int delta, bool additive)
+{
+    unsigned numItems = GetNumItems();
+    if (selections_.Empty())
+    {
+        // Select first item if there is no selection yet
+        if (numItems > 0)
+            SetSelection(0);
+        if (abs(delta) == 1)
+            return;
+    }
+    if (!multiselect_)
+        additive = false;
+
+    // If going downwards, use the last selection as a base. Otherwise use first
+    unsigned selection = delta > 0 ? selections_.Back() : selections_.Front();
+    int direction = delta > 0 ? 1 : -1;
+    unsigned newSelection = selection;
+    unsigned okSelection = selection;
+    PODVector<unsigned> indices = selections_;
+
+    while (delta != 0)
+    {
+        newSelection += direction;
+        if (newSelection >= numItems)
+            break;
+
+        UIElement* item = GetItem(newSelection);
+        if (item->IsVisible())
+        {
+            indices.Push(okSelection = newSelection);
+            delta -= direction;
+        }
+    }
+
+    if (!additive)
+        SetSelection(okSelection);
+    else
+        SetSelections(indices);
+}
+
+void ListView::ClearSelection()
+{
+    SetSelections(PODVector<unsigned>());
+}
+
+void ListView::SetHighlightMode(HighlightMode mode)
+{
+    highlightMode_ = mode;
+    UpdateSelectionEffect();
+}
+
+void ListView::SetMultiselect(bool enable)
+{
+    multiselect_ = enable;
+}
+
+void ListView::SetHierarchyMode(bool enable)
+{
+    if (enable == hierarchyMode_)
+        return;
+
+    hierarchyMode_ = enable;
+    UIElement* container;
+    if (enable)
+    {
+        overlayContainer_ = new UIElement(context_);
+        overlayContainer_->SetName("LV_OverlayContainer");
+        overlayContainer_->SetInternal(true);
+        AddChild(overlayContainer_);
+        overlayContainer_->SetSortChildren(false);
+        overlayContainer_->SetClipChildren(true);
+
+        container = new HierarchyContainer(context_, this, overlayContainer_);
+    }
+    else
+    {
+        if (overlayContainer_)
+        {
+            RemoveChild(overlayContainer_);
+            overlayContainer_.Reset();
+        }
+
+        container = new UIElement(context_);
+    }
+
+    container->SetName("LV_ItemContainer");
+    container->SetInternal(true);
+    SetContentElement(container);
+    container->SetEnabled(true);
+    container->SetSortChildren(false);
+}
+
+void ListView::SetBaseIndent(int baseIndent)
+{
+    baseIndent_ = baseIndent;
+    UpdateLayout();
+}
+
+void ListView::SetClearSelectionOnDefocus(bool enable)
+{
+    if (enable != clearSelectionOnDefocus_)
+    {
+        clearSelectionOnDefocus_ = enable;
+        if (clearSelectionOnDefocus_ && !HasFocus())
+            ClearSelection();
+    }
+}
+
+void ListView::SetSelectOnClickEnd(bool enable)
+{
+    if (enable != selectOnClickEnd_)
+    {
+        selectOnClickEnd_ = enable;
+        UpdateUIClickSubscription();
+    }
+}
+
+void ListView::Expand(unsigned index, bool enable, bool recursive)
+{
+    if (!hierarchyMode_)
+        return;
+
+    unsigned numItems = GetNumItems();
+    if (index >= numItems)
+        return;
+
+    UIElement* item = GetItem(index++);
+    SetItemExpanded(item, enable);
+    int baseIndent = item->GetIndent();
+
+    PODVector<bool> expanded((unsigned)(baseIndent + 1));
+    expanded[baseIndent] = enable;
+
+    contentElement_->DisableLayoutUpdate();
+
+    while (index < numItems)
+    {
+        item = GetItem(index++);
+        int indent = item->GetIndent();
+        if (indent <= baseIndent)
+            break;
+
+        // Propagate the state to children when it is recursive
+        if (recursive)
+            SetItemExpanded(item, enable);
+
+        // Use the parent expanded flag to influence the visibility of its children
+        bool visible = enable && expanded[indent - 1];
+        item->SetVisible(visible);
+
+        if (indent >= (int)expanded.Size())
+            expanded.Resize((unsigned)(indent + 1));
+        expanded[indent] = visible && GetItemExpanded(item);
+    }
+
+    contentElement_->EnableLayoutUpdate();
+    contentElement_->UpdateLayout();
+}
+
+void ListView::ToggleExpand(unsigned index, bool recursive)
+{
+    if (!hierarchyMode_)
+        return;
+
+    unsigned numItems = GetNumItems();
+    if (index >= numItems)
+        return;
+
+    UIElement* item = GetItem(index);
+    Expand(index, !GetItemExpanded(item), recursive);
+}
+
+unsigned ListView::GetNumItems() const
+{
+    return contentElement_->GetNumChildren();
+}
+
+UIElement* ListView::GetItem(unsigned index) const
+{
+    return contentElement_->GetChild(index);
+}
+
+PODVector<UIElement*> ListView::GetItems() const
+{
+    PODVector<UIElement*> items;
+    contentElement_->GetChildren(items);
+    return items;
+}
+
+unsigned ListView::FindItem(UIElement* item) const
+{
+    if (!item)
+        return M_MAX_UNSIGNED;
+
+    // Early-out by checking if the item belongs to the listview hierarchy at all
+    if (item->GetParent() != contentElement_)
+        return M_MAX_UNSIGNED;
+
+    const Vector<SharedPtr<UIElement> >& children = contentElement_->GetChildren();
+
+    // Binary search for list item based on screen coordinate Y
+    if (contentElement_->GetLayoutMode() == LM_VERTICAL && item->GetHeight())
+    {
+        int itemY = item->GetScreenPosition().y_;
+        int left = 0;
+        int right = children.Size() - 1;
+        while (right >= left)
+        {
+            int mid = (left + right) / 2;
+            if (children[mid] == item)
+                return (unsigned)mid;
+            if (itemY < children[mid]->GetScreenPosition().y_)
+                right = mid - 1;
+            else
+                left = mid + 1;
+        }
+    }
+
+    // Fallback to linear search in case the coordinates/sizes were not yet initialized
+    for (unsigned i = 0; i < children.Size(); ++i)
+    {
+        if (children[i] == item)
+            return i;
+    }
+
+    return M_MAX_UNSIGNED;
+}
+
+unsigned ListView::GetSelection() const
+{
+    if (selections_.Empty())
+        return M_MAX_UNSIGNED;
+    else
+        return GetSelections().Front();
+}
+
+UIElement* ListView::GetSelectedItem() const
+{
+    return contentElement_->GetChild(GetSelection());
+}
+
+PODVector<UIElement*> ListView::GetSelectedItems() const
+{
+    PODVector<UIElement*> ret;
+
+    for (PODVector<unsigned>::ConstIterator i = selections_.Begin(); i != selections_.End(); ++i)
+    {
+        UIElement* item = GetItem(*i);
+        if (item)
+            ret.Push(item);
+    }
+
+    return ret;
+}
+
+void ListView::CopySelectedItemsToClipboard() const
+{
+    String selectedText;
+
+    for (PODVector<unsigned>::ConstIterator i = selections_.Begin(); i != selections_.End(); ++i)
+    {
+        // Only handle Text UI element
+        Text* text = dynamic_cast<Text*>(GetItem(*i));
+        if (text)
+            selectedText.Append(text->GetText()).Append("\n");
+    }
+
+    GetSubsystem<SystemUI>()->SetClipboardText(selectedText);
+}
+
+bool ListView::IsSelected(unsigned index) const
+{
+    return selections_.Contains(index);
+}
+
+bool ListView::IsExpanded(unsigned index) const
+{
+    return GetItemExpanded(contentElement_->GetChild(index));
+}
+
+bool ListView::FilterImplicitAttributes(XMLElement& dest) const
+{
+    if (!ScrollView::FilterImplicitAttributes(dest))
+        return false;
+
+    XMLElement childElem = dest.GetChild("element");    // Horizontal scroll bar
+    if (!childElem)
+        return false;
+    childElem = childElem.GetNext("element");           // Vertical scroll bar
+    if (!childElem)
+        return false;
+    childElem = childElem.GetNext("element");           // Scroll panel
+    if (!childElem)
+        return false;
+
+    XMLElement containerElem = childElem.GetChild("element");   // Item container
+    if (!containerElem)
+        return false;
+    if (!RemoveChildXML(containerElem, "Name", "LV_ItemContainer"))
+        return false;
+    if (!RemoveChildXML(containerElem, "Is Enabled", "true"))
+        return false;
+    if (!RemoveChildXML(containerElem, "Layout Mode", "Vertical"))
+        return false;
+    if (!RemoveChildXML(containerElem, "Size"))
+        return false;
+
+    if (hierarchyMode_)
+    {
+        containerElem = childElem.GetNext("element");           // Overlay container
+        if (!containerElem)
+            return false;
+        if (!RemoveChildXML(containerElem, "Name", "LV_OverlayContainer"))
+            return false;
+        if (!RemoveChildXML(containerElem, "Clip Children", "true"))
+            return false;
+        if (!RemoveChildXML(containerElem, "Size"))
+            return false;
+    }
+
+    return true;
+}
+
+void ListView::UpdateSelectionEffect()
+{
+    unsigned numItems = GetNumItems();
+    bool highlighted = highlightMode_ == HM_ALWAYS || HasFocus();
+
+    for (unsigned i = 0; i < numItems; ++i)
+    {
+        UIElement* item = GetItem(i);
+        if (highlightMode_ != HM_NEVER && selections_.Contains(i))
+            item->SetSelected(highlighted);
+        else
+            item->SetSelected(false);
+    }
+}
+
+void ListView::EnsureItemVisibility(unsigned index)
+{
+    EnsureItemVisibility(GetItem(index));
+}
+
+void ListView::EnsureItemVisibility(UIElement* item)
+{
+    if (!item || !item->IsVisible())
+        return;
+
+    IntVector2 newView = GetViewPosition();
+    IntVector2 currentOffset = item->GetPosition() - newView;
+    const IntRect& clipBorder = scrollPanel_->GetClipBorder();
+    IntVector2 windowSize(scrollPanel_->GetWidth() - clipBorder.left_ - clipBorder.right_,
+        scrollPanel_->GetHeight() - clipBorder.top_ - clipBorder.bottom_);
+
+    if (currentOffset.y_ < 0)
+        newView.y_ += currentOffset.y_;
+    if (currentOffset.y_ + item->GetHeight() > windowSize.y_)
+        newView.y_ += currentOffset.y_ + item->GetHeight() - windowSize.y_;
+
+    SetViewPosition(newView);
+}
+
+void ListView::HandleUIMouseClick(StringHash eventType, VariantMap& eventData)
+{
+    // Disregard the click end if a drag is going on
+    if (selectOnClickEnd_ && GetSubsystem<SystemUI>()->IsDragging())
+        return;
+
+    int button = eventData[UIMouseClick::P_BUTTON].GetInt();
+    int buttons = eventData[UIMouseClick::P_BUTTONS].GetInt();
+    int qualifiers = eventData[UIMouseClick::P_QUALIFIERS].GetInt();
+
+    UIElement* element = static_cast<UIElement*>(eventData[UIMouseClick::P_ELEMENT].GetPtr());
+
+    // Check if the clicked element belongs to the list
+    unsigned i = FindItem(element);
+    if (i >= GetNumItems())
+        return;
+
+    // If not editable, repeat the previous selection. This will send an event and allow eg. a dropdownlist to close
+    if (!editable_)
+    {
+        SetSelections(selections_);
+        return;
+    }
+
+    if (button == MOUSEB_LEFT)
+    {
+        // Single selection
+        if (!multiselect_ || !qualifiers)
+            SetSelection(i);
+
+        // Check multiselect with shift & ctrl
+        if (multiselect_)
+        {
+            if (qualifiers & QUAL_SHIFT)
+            {
+                if (selections_.Empty())
+                    SetSelection(i);
+                else
+                {
+                    unsigned first = selections_.Front();
+                    unsigned last = selections_.Back();
+                    PODVector<unsigned> newSelections = selections_;
+                    if (i == first || i == last)
+                    {
+                        for (unsigned j = first; j <= last; ++j)
+                            newSelections.Push(j);
+                    }
+                    else if (i < first)
+                    {
+                        for (unsigned j = i; j <= first; ++j)
+                            newSelections.Push(j);
+                    }
+                    else if (i < last)
+                    {
+                        if ((abs((int)i - (int)first)) <= (abs((int)i - (int)last)))
+                        {
+                            for (unsigned j = first; j <= i; ++j)
+                                newSelections.Push(j);
+                        }
+                        else
+                        {
+                            for (unsigned j = i; j <= last; ++j)
+                                newSelections.Push(j);
+                        }
+                    }
+                    else if (i > last)
+                    {
+                        for (unsigned j = last; j <= i; ++j)
+                            newSelections.Push(j);
+                    }
+                    SetSelections(newSelections);
+                }
+            }
+            else if (qualifiers & QUAL_CTRL)
+                ToggleSelection(i);
+        }
+    }
+
+    // Propagate the click as an event. Also include right-clicks
+    VariantMap& clickEventData = GetEventDataMap();
+    clickEventData[ItemClicked::P_ELEMENT] = this;
+    clickEventData[ItemClicked::P_ITEM] = element;
+    clickEventData[ItemClicked::P_SELECTION] = i;
+    clickEventData[ItemClicked::P_BUTTON] = button;
+    clickEventData[ItemClicked::P_BUTTONS] = buttons;
+    clickEventData[ItemClicked::P_QUALIFIERS] = qualifiers;
+    SendEvent(E_ITEMCLICKED, clickEventData);
+}
+
+void ListView::HandleUIMouseDoubleClick(StringHash eventType, VariantMap& eventData)
+{
+    int button = eventData[UIMouseClick::P_BUTTON].GetInt();
+    int buttons = eventData[UIMouseClick::P_BUTTONS].GetInt();
+    int qualifiers = eventData[UIMouseClick::P_QUALIFIERS].GetInt();
+
+    UIElement* element = static_cast<UIElement*>(eventData[UIMouseClick::P_ELEMENT].GetPtr());
+    // Check if the clicked element belongs to the list
+    unsigned i = FindItem(element);
+    if (i >= GetNumItems())
+        return;
+
+    VariantMap& clickEventData = GetEventDataMap();
+    clickEventData[ItemDoubleClicked::P_ELEMENT] = this;
+    clickEventData[ItemDoubleClicked::P_ITEM] = element;
+    clickEventData[ItemDoubleClicked::P_SELECTION] = i;
+    clickEventData[ItemDoubleClicked::P_BUTTON] = button;
+    clickEventData[ItemDoubleClicked::P_BUTTONS] = buttons;
+    clickEventData[ItemDoubleClicked::P_QUALIFIERS] = qualifiers;
+    SendEvent(E_ITEMDOUBLECLICKED, clickEventData);
+}
+
+
+void ListView::HandleItemFocusChanged(StringHash eventType, VariantMap& eventData)
+{
+    using namespace FocusChanged;
+
+    UIElement* element = static_cast<UIElement*>(eventData[P_ELEMENT].GetPtr());
+    while (element)
+    {
+        // If the focused element or its parent is in the list, scroll the list to make the item visible
+        UIElement* parent = element->GetParent();
+        if (parent == contentElement_)
+        {
+            EnsureItemVisibility(element);
+            return;
+        }
+        element = parent;
+    }
+}
+
+void ListView::HandleFocusChanged(StringHash eventType, VariantMap& eventData)
+{
+    scrollPanel_->SetSelected(eventType == E_FOCUSED);
+    if (clearSelectionOnDefocus_ && eventType == E_DEFOCUSED)
+        ClearSelection();
+    else if (highlightMode_ == HM_FOCUS)
+        UpdateSelectionEffect();
+}
+
+void ListView::UpdateUIClickSubscription()
+{
+    UnsubscribeFromEvent(E_UIMOUSECLICK);
+    UnsubscribeFromEvent(E_UIMOUSECLICKEND);
+    SubscribeToEvent(selectOnClickEnd_ ? E_UIMOUSECLICKEND : E_UIMOUSECLICK, HANDLER(ListView, HandleUIMouseClick));
+}
+
+}
+
+}

+ 195 - 0
Source/Atomic/UI/SystemUI/ListView.h

@@ -0,0 +1,195 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "ScrollView.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+
+/// %ListView selection highlight mode.
+enum HighlightMode
+{
+    /// Never highlight selections.
+    HM_NEVER,
+    /// Highlight when focused.
+    HM_FOCUS,
+    /// Highlight always.
+    HM_ALWAYS
+};
+
+/// Scrollable list %UI element.
+class ATOMIC_API ListView : public ScrollView
+{
+    OBJECT(ListView);
+
+public:
+    /// Construct.
+    ListView(Context* context);
+    /// Destruct.
+    virtual ~ListView();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// React to a key press.
+    virtual void OnKey(int key, int buttons, int qualifiers);
+    /// React to resize.
+    virtual void OnResize();
+
+    /// Add item to the end of the list.
+    void AddItem(UIElement* item);
+    /// \brief Insert item at a specific index. In hierarchy mode, the optional parameter will be used to determine the child's indent level in respect to its parent.
+    /// If index is greater than the total items then the new item is inserted at the end of the list.
+    /// In hierarchy mode, if index is greater than the index of last children of the specified parent item then the new item is inserted next to the last children.
+    /// And if the index is lesser than the index of the parent item itself then the new item is inserted before the first child item.
+    void InsertItem(unsigned index, UIElement* item, UIElement* parentItem = 0);
+    /// Remove specific item, starting search at the specified index if provided. In hierarchy mode will also remove any children.
+    void RemoveItem(UIElement* item, unsigned index = 0);
+    /// Remove item at index. In hierarchy mode will also remove any children.
+    void RemoveItem(unsigned index);
+    /// Remove all items.
+    void RemoveAllItems();
+    /// Set selection.
+    void SetSelection(unsigned index);
+    /// Set multiple selected items. If multiselect disabled, sets only the first.
+    void SetSelections(const PODVector<unsigned>& indices);
+    /// Add item to the selection, multiselect mode only.
+    void AddSelection(unsigned index);
+    /// Remove item from the selection.
+    void RemoveSelection(unsigned index);
+    /// Toggle selection of an item.
+    void ToggleSelection(unsigned index);
+    /// Move selection by a delta and clamp at list ends. If additive (multiselect only), will add to the existing selection.
+    void ChangeSelection(int delta, bool additive = false);
+    /// Clear selection.
+    void ClearSelection();
+    /// Set selected items' highlight mode.
+    void SetHighlightMode(HighlightMode mode);
+    /// Enable multiselect.
+    void SetMultiselect(bool enable);
+    /// \brief Enable hierarchy mode. Allows items to have parent-child relationship at different indent level and the ability to expand/collapse child items.
+    /// All items in the list will be lost during mode change.
+    void SetHierarchyMode(bool enable);
+    /// Set base indent, i.e. the indent level of the ultimate parent item.
+    void SetBaseIndent(int baseIndent);
+    /// Enable clearing of selection on defocus.
+    void SetClearSelectionOnDefocus(bool enable);
+    /// Enable reacting to click end instead of click start for item selection. Default false.
+    void SetSelectOnClickEnd(bool enable);
+
+    /// Expand item at index. Only has effect in hierarchy mode.
+    void Expand(unsigned index, bool enable, bool recursive = false);
+    /// Toggle item's expanded flag at index. Only has effect in hierarchy mode.
+    void ToggleExpand(unsigned index, bool recursive = false);
+
+    /// Return number of items.
+    unsigned GetNumItems() const;
+    /// Return item at index.
+    UIElement* GetItem(unsigned index) const;
+    /// Return all items.
+    PODVector<UIElement*> GetItems() const;
+    /// Return index of item, or M_MAX_UNSIGNED If not found.
+    unsigned FindItem(UIElement* item) const;
+    /// Return first selected index, or M_MAX_UNSIGNED if none selected.
+    unsigned GetSelection() const;
+
+    /// Return all selected indices.
+    const PODVector<unsigned>& GetSelections() const { return selections_; }
+
+    /// Copy selected items to system clipboard. Currently only applicable to Text items.
+    void CopySelectedItemsToClipboard() const;
+    /// Return first selected item, or null if none selected.
+    UIElement* GetSelectedItem() const;
+    /// Return all selected items.
+    PODVector<UIElement*> GetSelectedItems() const;
+    /// Return whether an item at index is seleccted.
+    bool IsSelected(unsigned index) const;
+    /// Return whether an item at index has its children expanded (in hierachy mode only).
+    bool IsExpanded(unsigned index) const;
+
+    /// Return highlight mode.
+    HighlightMode GetHighlightMode() const { return highlightMode_; }
+
+    /// Return whether multiselect enabled.
+    bool GetMultiselect() const { return multiselect_; }
+
+    /// Return whether selection is cleared on defocus.
+    bool GetClearSelectionOnDefocus() const { return clearSelectionOnDefocus_; }
+
+    /// Return whether reacts to click end instead of click start for item selection.
+    bool GetSelectOnClickEnd() const { return selectOnClickEnd_; }
+
+    /// Return whether hierarchy mode enabled.
+    bool GetHierarchyMode() const { return hierarchyMode_; }
+
+    /// Return base indent.
+    int GetBaseIndent() const { return baseIndent_; }
+
+    /// Ensure full visibility of the item.
+    void EnsureItemVisibility(unsigned index);
+    /// Ensure full visibility of the item.
+    void EnsureItemVisibility(UIElement* item);
+
+protected:
+    /// Filter implicit attributes in serialization process.
+    virtual bool FilterImplicitAttributes(XMLElement& dest) const;
+    /// Update selection effect when selection or focus changes.
+    void UpdateSelectionEffect();
+
+    /// Current selection.
+    PODVector<unsigned> selections_;
+    /// Highlight mode.
+    HighlightMode highlightMode_;
+    /// Multiselect flag.
+    bool multiselect_;
+    /// Hierarchy mode flag.
+    bool hierarchyMode_;
+    /// Base indent, used in hierarchy mode only.
+    int baseIndent_;
+    /// Overlay container, used in hierarchy mode only.
+    SharedPtr<UIElement> overlayContainer_;
+    /// Clear selection on defocus flag.
+    bool clearSelectionOnDefocus_;
+    /// React to click end instead of click start flag.
+    bool selectOnClickEnd_;
+
+private:
+    /// Handle global UI mouseclick to check for selection change.
+    void HandleUIMouseClick(StringHash eventType, VariantMap& eventData);
+    /// Handle global UI mouse doubleclick.
+    void HandleUIMouseDoubleClick(StringHash eventType, VariantMap& eventData);
+    /// Handle global focus change to check whether an invisible item was focused.
+    void HandleItemFocusChanged(StringHash eventType, VariantMap& eventData);
+    /// Handle focus changed.
+    void HandleFocusChanged(StringHash eventType, VariantMap& eventData);
+    /// Update subscription to UI click events
+    void UpdateUIClickSubscription();
+};
+
+}
+
+}

+ 444 - 0
Source/Atomic/UI/SystemUI/Menu.cpp

@@ -0,0 +1,444 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Input/InputEvents.h"
+#include "../../IO/Log.h"
+#include "Menu.h"
+#include "SystemUI.h"
+#include "SystemUIEvents.h"
+#include "Window.h"
+
+#include "../../DebugNew.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+const StringHash VAR_SHOW_POPUP("ShowPopup");
+extern StringHash VAR_ORIGIN;
+
+extern const char* UI_CATEGORY;
+
+Menu::Menu(Context* context) :
+    Button(context),
+    popupOffset_(IntVector2::ZERO),
+    showPopup_(false),
+    acceleratorKey_(0),
+    acceleratorQualifiers_(0),
+    autoPopup_(true)
+{
+    focusMode_ = FM_NOTFOCUSABLE;
+
+    SubscribeToEvent(this, E_PRESSED, HANDLER(Menu, HandlePressedReleased));
+    SubscribeToEvent(this, E_RELEASED, HANDLER(Menu, HandlePressedReleased));
+    SubscribeToEvent(E_UIMOUSECLICK, HANDLER(Menu, HandleFocusChanged));
+    SubscribeToEvent(E_FOCUSCHANGED, HANDLER(Menu, HandleFocusChanged));
+}
+
+Menu::~Menu()
+{
+    if (popup_ && showPopup_)
+        ShowPopup(false);
+}
+
+void Menu::RegisterObject(Context* context)
+{
+    context->RegisterFactory<Menu>(UI_CATEGORY);
+
+    COPY_BASE_ATTRIBUTES(Button);
+    UPDATE_ATTRIBUTE_DEFAULT_VALUE("Focus Mode", FM_NOTFOCUSABLE);
+    ACCESSOR_ATTRIBUTE("Popup Offset", GetPopupOffset, SetPopupOffset, IntVector2, IntVector2::ZERO, AM_FILE);
+}
+
+void Menu::Update(float timeStep)
+{
+    Button::Update(timeStep);
+
+    if (popup_ && showPopup_)
+    {
+        const Vector<SharedPtr<UIElement> >& children = popup_->GetChildren();
+        for (unsigned i = 0; i < children.Size(); ++i)
+        {
+            Menu* menu = dynamic_cast<Menu*>(children[i].Get());
+            if (menu && !menu->autoPopup_ && !menu->IsHovering())
+                menu->autoPopup_ = true;
+        }
+    }
+}
+
+void Menu::OnHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
+{
+    Button::OnHover(position, screenPosition, buttons, qualifiers, cursor);
+
+    Menu* sibling = static_cast<Menu*>(parent_->GetChild(VAR_SHOW_POPUP, true));
+    if (popup_ && !showPopup_)
+    {
+        // Check if popup is shown by one of the siblings
+        if (sibling)
+        {
+            // "Move" the popup from sibling menu to this menu
+            sibling->ShowPopup(false);
+            ShowPopup(true);
+            return;
+        }
+
+        if (autoPopup_)
+        {
+            // Show popup when parent menu has its popup shown
+            Menu* parentMenu = static_cast<Menu*>(parent_->GetVar(VAR_ORIGIN).GetPtr());
+            if (parentMenu && parentMenu->showPopup_)
+                ShowPopup(true);
+        }
+    }
+    else
+    {
+        // Hide child menu popup when its parent is no longer being hovered
+        if (sibling && sibling != this)
+            sibling->ShowPopup(false);
+    }
+}
+
+void Menu::OnShowPopup()
+{
+}
+
+bool Menu::LoadXML(const XMLElement& source, XMLFile* styleFile, bool setInstanceDefault)
+{
+    // Get style override if defined
+    String styleName = source.GetAttribute("style");
+
+    // Apply the style first, if the style file is available
+    if (styleFile)
+    {
+        // If not defined, use type name
+        if (styleName.Empty())
+            styleName = GetTypeName();
+
+        SetStyle(styleName, styleFile);
+    }
+    // The 'style' attribute value in the style file cannot be equals to original's applied style to prevent infinite loop
+    else if (!styleName.Empty() && styleName != appliedStyle_)
+    {
+        // Attempt to use the default style file
+        styleFile = GetDefaultStyle();
+
+        if (styleFile)
+        {
+            // Remember the original applied style
+            String appliedStyle(appliedStyle_);
+            SetStyle(styleName, styleFile);
+            appliedStyle_ = appliedStyle;
+        }
+    }
+
+    // Then load rest of the attributes from the source
+    if (!Serializable::LoadXML(source, setInstanceDefault))
+        return false;
+
+    unsigned nextInternalChild = 0;
+
+    // Load child elements. Internal elements are not to be created as they already exist
+    XMLElement childElem = source.GetChild("element");
+    while (childElem)
+    {
+        bool internalElem = childElem.GetBool("internal");
+        bool popupElem = childElem.GetBool("popup");
+        String typeName = childElem.GetAttribute("type");
+        if (typeName.Empty())
+            typeName = "UIElement";
+        unsigned index = childElem.HasAttribute("index") ? childElem.GetUInt("index") : M_MAX_UNSIGNED;
+        UIElement* child = 0;
+
+        if (!internalElem)
+        {
+            if (!popupElem)
+                child = CreateChild(typeName, String::EMPTY, index);
+            else
+            {
+                // Do not add the popup element as a child even temporarily, as that can break layouts
+                SharedPtr<UIElement> popup = DynamicCast<UIElement>(context_->CreateObject(typeName));
+                if (!popup)
+                    LOGERROR("Could not create popup element type " + typeName);
+                else
+                {
+                    child = popup;
+                    SetPopup(popup);
+                }
+            }
+        }
+        else
+        {
+            // An internal popup element should already exist
+            if (popupElem)
+                child = popup_;
+            else
+            {
+                for (unsigned i = nextInternalChild; i < children_.Size(); ++i)
+                {
+                    if (children_[i]->IsInternal() && children_[i]->GetTypeName() == typeName)
+                    {
+                        child = children_[i];
+                        nextInternalChild = i + 1;
+                        break;
+                    }
+                }
+
+                if (!child)
+                    LOGWARNING("Could not find matching internal child element of type " + typeName + " in " + GetTypeName());
+            }
+        }
+
+        if (child)
+        {
+            if (!styleFile)
+                styleFile = GetDefaultStyle();
+
+            // As popup is not a child element in itself, the parental chain to acquire the default style file is broken for popup's child elements
+            // To recover from this, popup needs to have the default style set in its own instance so the popup's child elements can find it later
+            if (popupElem)
+                child->SetDefaultStyle(styleFile);
+
+            if (!child->LoadXML(childElem, styleFile, setInstanceDefault))
+                return false;
+        }
+
+        childElem = childElem.GetNext("element");
+    }
+
+    ApplyAttributes();
+
+    return true;
+}
+
+bool Menu::SaveXML(XMLElement& dest) const
+{
+    if (!Button::SaveXML(dest))
+        return false;
+
+    // Save the popup element as a "virtual" child element
+    if (popup_)
+    {
+        XMLElement childElem = dest.CreateChild("element");
+        childElem.SetBool("popup", true);
+        if (!popup_->SaveXML(childElem))
+            return false;
+
+        // Filter popup implicit attributes
+        if (!FilterPopupImplicitAttributes(childElem))
+        {
+            LOGERROR("Could not remove popup implicit attributes");
+            return false;
+        }
+    }
+
+    return true;
+}
+
+void Menu::SetPopup(UIElement* popup)
+{
+    if (popup == this)
+        return;
+
+    // Currently only allow popup 'window'
+    if (popup->GetType() != Window::GetTypeStatic())
+    {
+        LOGERROR("Could not set popup element of type " + popup->GetTypeName() + ", only support popup window for now");
+        return;
+    }
+
+    if (popup_ && !popup)
+        ShowPopup(false);
+
+    popup_ = popup;
+
+    // Detach from current parent (if any) to only show when it is time
+    if (popup_)
+        popup_->Remove();
+}
+
+void Menu::SetPopupOffset(const IntVector2& offset)
+{
+    popupOffset_ = offset;
+}
+
+void Menu::SetPopupOffset(int x, int y)
+{
+    popupOffset_ = IntVector2(x, y);
+}
+
+void Menu::ShowPopup(bool enable)
+{
+    if (!popup_)
+        return;
+
+    if (enable)
+    {
+        OnShowPopup();
+
+        popup_->SetVar(VAR_ORIGIN, this);
+        static_cast<Window*>(popup_.Get())->SetModal(true);
+
+        popup_->SetPosition(GetScreenPosition() + popupOffset_);
+        popup_->SetVisible(true);
+        // BringToFront() is unreliable in this case as it takes into account only input-enabled elements.
+        // Rather just force priority to max
+        popup_->SetPriority(M_MAX_INT);
+    }
+    else
+    {
+        OnHidePopup();
+
+        // If the popup has child menus, hide their popups as well
+        PODVector<UIElement*> children;
+        popup_->GetChildren(children, true);
+        for (PODVector<UIElement*>::ConstIterator i = children.Begin(); i != children.End(); ++i)
+        {
+            Menu* menu = dynamic_cast<Menu*>(*i);
+            if (menu)
+                menu->ShowPopup(false);
+        }
+
+        static_cast<Window*>(popup_.Get())->SetModal(false);
+        const_cast<VariantMap&>(popup_->GetVars()).Erase(VAR_ORIGIN);
+
+        popup_->SetVisible(false);
+        popup_->Remove();
+    }
+    SetVar(VAR_SHOW_POPUP, enable);
+
+    showPopup_ = enable;
+    selected_ = enable;
+}
+
+void Menu::SetAccelerator(int key, int qualifiers)
+{
+    acceleratorKey_ = key;
+    acceleratorQualifiers_ = qualifiers;
+
+    if (key)
+        SubscribeToEvent(E_KEYDOWN, HANDLER(Menu, HandleKeyDown));
+    else
+        UnsubscribeFromEvent(E_KEYDOWN);
+}
+
+bool Menu::FilterPopupImplicitAttributes(XMLElement& dest) const
+{
+    if (!RemoveChildXML(dest, "Position"))
+        return false;
+    if (!RemoveChildXML(dest, "Is Visible"))
+        return false;
+
+    return true;
+}
+
+void Menu::HandlePressedReleased(StringHash eventType, VariantMap& eventData)
+{
+    // If this menu shows a sublevel popup, react to button press. Else react to release
+    if (eventType == E_PRESSED)
+    {
+        if (!popup_)
+            return;
+    }
+    if (eventType == E_RELEASED)
+    {
+        if (popup_)
+            return;
+    }
+
+    // Manual handling of the popup, so switch off the auto popup flag
+    autoPopup_ = false;
+    // Toggle popup visibility if exists
+    ShowPopup(!showPopup_);
+
+    // Send event on each click if no popup, or whenever the popup is opened
+    if (!popup_ || showPopup_)
+    {
+        using namespace MenuSelected;
+
+        VariantMap& newEventData = GetEventDataMap();
+        newEventData[P_ELEMENT] = this;
+        SendEvent(E_MENUSELECTED, newEventData);
+    }
+}
+
+void Menu::HandleFocusChanged(StringHash eventType, VariantMap& eventData)
+{
+    if (!showPopup_)
+        return;
+
+    using namespace FocusChanged;
+
+    UIElement* element = static_cast<UIElement*>(eventData[P_ELEMENT].GetPtr());
+    UIElement* root = GetRoot();
+
+    // If another element was focused due to the menu button being clicked, do not hide the popup
+    if (eventType == E_FOCUSCHANGED && static_cast<UIElement*>(eventData[P_CLICKEDELEMENT].GetPtr()))
+        return;
+
+    // If clicked emptiness or defocused, hide the popup
+    if (!element)
+    {
+        ShowPopup(false);
+        return;
+    }
+
+    // Otherwise see if the clicked element has either the menu item or the popup in its parent chain.
+    // In that case, do not hide
+    while (element)
+    {
+        if (element == this || element == popup_)
+            return;
+        if (element->GetParent() == root)
+            element = static_cast<UIElement*>(element->GetVar(VAR_ORIGIN).GetPtr());
+        else
+            element = element->GetParent();
+    }
+
+    ShowPopup(false);
+}
+
+void Menu::HandleKeyDown(StringHash eventType, VariantMap& eventData)
+{
+    if (!enabled_)
+        return;
+
+    using namespace KeyDown;
+
+    // Activate if accelerator key pressed
+    if (eventData[P_KEY].GetInt() == acceleratorKey_ &&
+        (acceleratorQualifiers_ == QUAL_ANY || eventData[P_QUALIFIERS].GetInt() == acceleratorQualifiers_) &&
+        eventData[P_REPEAT].GetBool() == false)
+    {
+        // Ignore if UI has modal element
+        if (GetSubsystem<SystemUI>()->HasModalElement())
+            return;
+
+        HandlePressedReleased(eventType, eventData);
+    }
+}
+
+}
+
+}

+ 117 - 0
Source/Atomic/UI/SystemUI/Menu.h

@@ -0,0 +1,117 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "Button.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+
+/// %Menu %UI element that optionally shows a popup.
+class ATOMIC_API Menu : public Button
+{
+    OBJECT(Menu);
+
+    using UIElement::LoadXML;
+
+public:
+    /// Construct.
+    Menu(Context* context);
+    /// Destruct.
+    virtual ~Menu();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Load from XML data with style. Return true if successful.
+    virtual bool LoadXML(const XMLElement& source, XMLFile* styleFile, bool setInstanceDefault = false);
+    /// Save as XML data. Return true if successful.
+    virtual bool SaveXML(XMLElement& dest) const;
+
+    /// Perform UI element update.
+    virtual void Update(float timeStep);
+    /// React to mouse hover.
+    virtual void OnHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
+    /// React to the popup being shown.
+    virtual void OnShowPopup();
+
+    /// React to the popup being hidden.
+    virtual void OnHidePopup() { }
+
+    /// Set popup element to show on selection.
+    void SetPopup(UIElement* element);
+    /// Set popup element offset.
+    void SetPopupOffset(const IntVector2& offset);
+    /// Set popup element offset.
+    void SetPopupOffset(int x, int y);
+    /// Force the popup to show or hide.
+    void ShowPopup(bool enable);
+    /// Set accelerator key (set zero key code to disable.)
+    void SetAccelerator(int key, int qualifiers);
+
+    /// Return popup element.
+    UIElement* GetPopup() const { return popup_; }
+
+    /// Return popup element offset.
+    const IntVector2& GetPopupOffset() const { return popupOffset_; }
+
+    /// Return whether popup is open.
+    bool GetShowPopup() const { return showPopup_; }
+
+    /// Return accelerator key code, 0 if disabled.
+    int GetAcceleratorKey() const { return acceleratorKey_; }
+
+    /// Return accelerator qualifiers.
+    int GetAcceleratorQualifiers() const { return acceleratorQualifiers_; }
+
+protected:
+    /// Filter implicit attributes in serialization process.
+    virtual bool FilterPopupImplicitAttributes(XMLElement& dest) const;
+    /// Popup element.
+    SharedPtr<UIElement> popup_;
+    /// Popup element offset.
+    IntVector2 popupOffset_;
+    /// Show popup flag.
+    bool showPopup_;
+    /// Accelerator key code.
+    int acceleratorKey_;
+    /// Accelerator qualifiers.
+    int acceleratorQualifiers_;
+
+private:
+    /// Handle press and release for selection and toggling popup visibility.
+    void HandlePressedReleased(StringHash eventType, VariantMap& eventData);
+    /// Handle global focus change to check for hiding the popup.
+    void HandleFocusChanged(StringHash eventType, VariantMap& eventData);
+    /// Handle keypress for checking accelerator.
+    void HandleKeyDown(StringHash eventType, VariantMap& eventData);
+    /// Auto popup flag.
+    bool autoPopup_;
+};
+
+}
+
+}

+ 150 - 0
Source/Atomic/UI/SystemUI/MessageBox.cpp

@@ -0,0 +1,150 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Graphics/Graphics.h"
+#include "../../IO/Log.h"
+#include "../../Resource/ResourceCache.h"
+#include "../../Resource/XMLFile.h"
+#include "Button.h"
+#include "MessageBox.h"
+#include "Text.h"
+#include "SystemUI.h"
+#include "SystemUIEvents.h"
+#include "Window.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+
+MessageBox::MessageBox(Context* context, const String& messageString, const String& titleString, XMLFile* layoutFile,
+    XMLFile* styleFile) :
+    Object(context),
+    titleText_(0),
+    messageText_(0),
+    okButton_(0)
+{
+    // If layout file is not given, use the default message box layout
+    if (!layoutFile)
+    {
+        ResourceCache* cache = GetSubsystem<ResourceCache>();
+        layoutFile = cache->GetResource<XMLFile>("UI/MessageBox.xml");
+        if (!layoutFile)    // Error is already logged
+            return;         // Note: windowless MessageBox should not be used!
+    }
+
+    SystemUI* ui = GetSubsystem<SystemUI>();
+    window_ = ui->LoadLayout(layoutFile, styleFile);
+    if (!window_)   // Error is already logged
+        return;
+    ui->GetRoot()->AddChild(window_);
+
+    // Set the title and message strings if they are given
+    titleText_ = dynamic_cast<Text*>(window_->GetChild("TitleText", true));
+    if (titleText_ && !titleString.Empty())
+        titleText_->SetText(titleString);
+    messageText_ = dynamic_cast<Text*>(window_->GetChild("MessageText", true));
+    if (messageText_ && !messageString.Empty())
+        messageText_->SetText(messageString);
+
+    // Center window after the message is set
+    Window* window = dynamic_cast<Window*>(window_.Get());
+    if (window)
+    {
+        Graphics* graphics = GetSubsystem<Graphics>();  // May be null if headless
+        if (graphics)
+        {
+            const IntVector2& size = window->GetSize();
+            window->SetPosition((graphics->GetWidth() - size.x_) / 2, (graphics->GetHeight() - size.y_) / 2);
+        }
+        else
+            LOGWARNING("Instantiating a modal window in headless mode!");
+
+        window->SetModal(true);
+        SubscribeToEvent(window, E_MODALCHANGED, HANDLER(MessageBox, HandleMessageAcknowledged));
+    }
+
+    // Bind the buttons (if any in the loaded UI layout) to event handlers
+    okButton_ = dynamic_cast<Button*>(window_->GetChild("OkButton", true));
+    if (okButton_)
+    {
+        ui->SetFocusElement(okButton_);
+        SubscribeToEvent(okButton_, E_RELEASED, HANDLER(MessageBox, HandleMessageAcknowledged));
+    }
+    Button* cancelButton = dynamic_cast<Button*>(window_->GetChild("CancelButton", true));
+    if (cancelButton)
+        SubscribeToEvent(cancelButton, E_RELEASED, HANDLER(MessageBox, HandleMessageAcknowledged));
+    Button* closeButton = dynamic_cast<Button*>(window_->GetChild("CloseButton", true));
+    if (closeButton)
+        SubscribeToEvent(closeButton, E_RELEASED, HANDLER(MessageBox, HandleMessageAcknowledged));
+}
+
+MessageBox::~MessageBox()
+{
+    if (window_)
+        window_->Remove();
+}
+
+void MessageBox::RegisterObject(Context* context)
+{
+    context->RegisterFactory<MessageBox>();
+}
+
+void MessageBox::SetTitle(const String& text)
+{
+    if (titleText_)
+        titleText_->SetText(text);
+}
+
+void MessageBox::SetMessage(const String& text)
+{
+    if (messageText_)
+        messageText_->SetText(text);
+}
+
+const String& MessageBox::GetTitle() const
+{
+    return titleText_ ? titleText_->GetText() : String::EMPTY;
+}
+
+const String& MessageBox::GetMessage() const
+{
+    return messageText_ ? messageText_->GetText() : String::EMPTY;
+}
+
+void MessageBox::HandleMessageAcknowledged(StringHash eventType, VariantMap& eventData)
+{
+    using namespace MessageACK;
+
+    VariantMap& newEventData = GetEventDataMap();
+    newEventData[P_OK] = eventData[Released::P_ELEMENT] == okButton_;
+    SendEvent(E_MESSAGEACK, newEventData);
+
+    this->ReleaseRef();
+}
+
+}
+
+}

+ 82 - 0
Source/Atomic/UI/SystemUI/MessageBox.h

@@ -0,0 +1,82 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "../../Core/Object.h"
+
+namespace Atomic
+{
+
+class XMLFile;
+
+namespace SystemUI
+{
+
+class Button;
+class Text;
+class UIElement;
+
+/// Message box dialog.
+class ATOMIC_API MessageBox : public Object
+{
+    OBJECT(MessageBox);
+
+public:
+    /// Construct. If layout file is not given, use the default message box layout. If style file is not given, use the default style file from root UI element.
+    MessageBox(Context* context, const String& messageString = String::EMPTY, const String& titleString = String::EMPTY,
+        XMLFile* layoutFile = 0, XMLFile* styleFile = 0);
+    /// Destruct.
+    virtual ~MessageBox();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Set title text. No-ops if there is no title text element.
+    void SetTitle(const String& text);
+    /// Set message text. No-ops if there is no message text element.
+    void SetMessage(const String& text);
+
+    /// Return title text. Return empty string if there is no title text element.
+    const String& GetTitle() const;
+    /// Return message text. Return empty string if there is no message text element.
+    const String& GetMessage() const;
+
+    /// Return dialog window.
+    UIElement* GetWindow() const { return window_; }
+
+private:
+    /// Handle events that dismiss the message box.
+    void HandleMessageAcknowledged(StringHash eventType, VariantMap& eventData);
+
+    /// UI element containing the whole UI layout. Typically it is a Window element type.
+    SharedPtr<UIElement> window_;
+    /// Title text element.
+    Text* titleText_;
+    /// Message text element.
+    Text* messageText_;
+    /// OK button element.
+    Button* okButton_;
+};
+
+}
+
+}

+ 329 - 0
Source/Atomic/UI/SystemUI/ScrollBar.cpp

@@ -0,0 +1,329 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Input/InputEvents.h"
+#include "Button.h"
+#include "ScrollBar.h"
+#include "Slider.h"
+#include "SystemUIEvents.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+
+static const float DEFAULT_SCROLL_STEP = 0.1f;
+static const float DEFAULT_REPEAT_DELAY = 0.4f;
+static const float DEFAULT_REPEAT_RATE = 20.0f;
+
+extern const char* orientations[];
+extern const char* UI_CATEGORY;
+
+ScrollBar::ScrollBar(Context* context) :
+    UIElement(context),
+    scrollStep_(DEFAULT_SCROLL_STEP),
+    stepFactor_(1.0f),
+    leftRect_(IntRect::ZERO),
+    rightRect_(IntRect::ZERO),
+    upRect_(IntRect::ZERO),
+    downRect_(IntRect::ZERO)
+{
+    SetEnabled(true);
+
+    backButton_ = CreateChild<Button>("SB_Back");
+    backButton_->SetInternal(true);
+    backButton_->SetRepeat(DEFAULT_REPEAT_DELAY, DEFAULT_REPEAT_RATE);
+    backButton_->SetFocusMode(FM_NOTFOCUSABLE);
+    slider_ = CreateChild<Slider>("SB_Slider");
+    slider_->SetInternal(true);
+    slider_->SetRepeatRate(DEFAULT_REPEAT_RATE);
+    forwardButton_ = CreateChild<Button>("SB_Forward");
+    forwardButton_->SetInternal(true);
+    forwardButton_->SetRepeat(DEFAULT_REPEAT_DELAY, DEFAULT_REPEAT_RATE);
+    forwardButton_->SetFocusMode(FM_NOTFOCUSABLE);
+
+    SubscribeToEvent(backButton_, E_PRESSED, HANDLER(ScrollBar, HandleBackButtonPressed));
+    SubscribeToEvent(forwardButton_, E_PRESSED, HANDLER(ScrollBar, HandleForwardButtonPressed));
+    SubscribeToEvent(slider_, E_SLIDERCHANGED, HANDLER(ScrollBar, HandleSliderChanged));
+    SubscribeToEvent(slider_, E_SLIDERPAGED, HANDLER(ScrollBar, HandleSliderPaged));
+
+    // Set default orientation
+    SetOrientation(O_HORIZONTAL);
+}
+
+ScrollBar::~ScrollBar()
+{
+}
+
+void ScrollBar::RegisterObject(Context* context)
+{
+    context->RegisterFactory<ScrollBar>(UI_CATEGORY);
+
+    COPY_BASE_ATTRIBUTES(UIElement);
+    UPDATE_ATTRIBUTE_DEFAULT_VALUE("Is Enabled", true);
+    ENUM_ACCESSOR_ATTRIBUTE("Orientation", GetOrientation, SetOrientation, Orientation, orientations, O_HORIZONTAL, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Range", GetRange, SetRange, float, 1.0f, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Value", GetValue, SetValue, float, 0.0f, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Scroll Step", GetScrollStep, SetScrollStep, float, DEFAULT_SCROLL_STEP, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Step Factor", GetStepFactor, SetStepFactor, float, 1.0f, AM_FILE);
+    ATTRIBUTE("Left Image Rect", IntRect, leftRect_, IntRect::ZERO, AM_FILE);
+    ATTRIBUTE("Right Image Rect", IntRect, rightRect_, IntRect::ZERO, AM_FILE);
+    ATTRIBUTE("Up Image Rect", IntRect, upRect_, IntRect::ZERO, AM_FILE);
+    ATTRIBUTE("Down Image Rect", IntRect, downRect_, IntRect::ZERO, AM_FILE);
+}
+
+void ScrollBar::ApplyAttributes()
+{
+    UIElement::ApplyAttributes();
+
+    // Reapply orientation to the button images
+    if (slider_->GetOrientation() == O_HORIZONTAL)
+    {
+        backButton_->SetImageRect(leftRect_);
+        forwardButton_->SetImageRect(rightRect_);
+    }
+    else
+    {
+        backButton_->SetImageRect(upRect_);
+        forwardButton_->SetImageRect(downRect_);
+    }
+}
+
+void ScrollBar::OnResize()
+{
+    if (slider_->GetOrientation() == O_HORIZONTAL)
+    {
+        int height = GetHeight();
+        int sliderWidth = Max(GetWidth() - 2 * height, 0);
+
+        backButton_->SetSize(height, height);
+        slider_->SetSize(sliderWidth, height);
+        forwardButton_->SetSize(height, height);
+
+        backButton_->SetPosition(0, 0);
+        slider_->SetPosition(height, 0);
+        forwardButton_->SetPosition(height + sliderWidth, 0);
+    }
+    else
+    {
+        int width = GetWidth();
+        int sliderHeight = Max(GetHeight() - 2 * width, 0);
+
+        backButton_->SetSize(width, width);
+        slider_->SetSize(width, sliderHeight);
+        forwardButton_->SetSize(width, width);
+
+        backButton_->SetPosition(0, 0);
+        slider_->SetPosition(0, width);
+        forwardButton_->SetPosition(0, sliderHeight + width);
+    }
+}
+
+void ScrollBar::OnSetEditable()
+{
+    slider_->SetEditable(editable_);
+}
+
+void ScrollBar::SetOrientation(Orientation orientation)
+{
+    slider_->SetOrientation(orientation);
+
+    if (orientation == O_HORIZONTAL)
+    {
+        backButton_->SetImageRect(leftRect_);
+        forwardButton_->SetImageRect(rightRect_);
+    }
+    else
+    {
+        backButton_->SetImageRect(upRect_);
+        forwardButton_->SetImageRect(downRect_);
+    }
+
+    OnResize();
+}
+
+void ScrollBar::SetRange(float range)
+{
+    slider_->SetRange(range);
+}
+
+void ScrollBar::SetValue(float value)
+{
+    slider_->SetValue(value);
+}
+
+void ScrollBar::ChangeValue(float delta)
+{
+    slider_->ChangeValue(delta);
+}
+
+void ScrollBar::SetScrollStep(float step)
+{
+    scrollStep_ = Max(step, 0.0f);
+}
+
+void ScrollBar::SetStepFactor(float factor)
+{
+    stepFactor_ = Max(factor, M_EPSILON);
+}
+
+void ScrollBar::StepBack()
+{
+    slider_->SetValue(slider_->GetValue() - GetEffectiveScrollStep());
+}
+
+void ScrollBar::StepForward()
+{
+    slider_->SetValue(slider_->GetValue() + GetEffectiveScrollStep());
+}
+
+Orientation ScrollBar::GetOrientation() const
+{
+    return slider_->GetOrientation();
+}
+
+float ScrollBar::GetRange() const
+{
+    return slider_->GetRange();
+}
+
+float ScrollBar::GetValue() const
+{
+    return slider_->GetValue();
+}
+
+float ScrollBar::GetEffectiveScrollStep() const
+{
+    return scrollStep_ * stepFactor_;
+}
+
+bool ScrollBar::FilterImplicitAttributes(XMLElement& dest) const
+{
+    if (!UIElement::FilterImplicitAttributes(dest))
+        return false;
+
+    if (!RemoveChildXML(dest, "Layout Mode"))
+        return false;
+
+    XMLElement childElem = dest.GetChild("element");
+    if (!FilterButtonImplicitAttributes(childElem, "SB_Back"))
+        return false;
+
+    childElem = childElem.GetNext("element");
+    if (!childElem)
+        return false;
+    if (!RemoveChildXML(childElem, "Name", "SB_Slider"))
+        return false;
+    if (!RemoveChildXML(childElem, "Repeat Rate", String(DEFAULT_REPEAT_RATE)))
+        return false;
+    if (!RemoveChildXML(childElem, "Orientation"))
+        return false;
+    if (!RemoveChildXML(childElem, "Range"))
+        return false;
+    if (!RemoveChildXML(childElem, "Value"))
+        return false;
+
+    childElem = childElem.GetNext("element");
+    return FilterButtonImplicitAttributes(childElem, "SB_Forward");
+
+}
+
+bool ScrollBar::FilterButtonImplicitAttributes(XMLElement& dest, const String& name) const
+{
+    if (!dest)
+        return false;
+    if (!RemoveChildXML(dest, "Name", name))
+        return false;
+    if (!RemoveChildXML(dest, "Repeat Delay", String(DEFAULT_REPEAT_DELAY)))
+        return false;
+    if (!RemoveChildXML(dest, "Repeat Rate", String(DEFAULT_REPEAT_RATE)))
+        return false;
+    if (!RemoveChildXML(dest, "Image Rect"))
+        return false;
+    if (!RemoveChildXML(dest, "Min Size"))
+        return false;
+    if (!RemoveChildXML(dest, "Max Size"))
+        return false;
+    if (!RemoveChildXML(dest, "Focus Mode", "NotFocusable"))
+        return false;
+
+    return true;
+}
+
+void ScrollBar::HandleBackButtonPressed(StringHash eventType, VariantMap& eventData)
+{
+    if (editable_)
+        StepBack();
+}
+
+void ScrollBar::HandleForwardButtonPressed(StringHash eventType, VariantMap& eventData)
+{
+    if (editable_)
+        StepForward();
+}
+
+void ScrollBar::HandleSliderChanged(StringHash eventType, VariantMap& eventData)
+{
+    // Send the event forward
+    VariantMap& newEventData = GetEventDataMap();
+    newEventData[ScrollBarChanged::P_ELEMENT] = this;
+    newEventData[ScrollBarChanged::P_VALUE] = slider_->GetValue();
+    SendEvent(E_SCROLLBARCHANGED, newEventData);
+}
+
+void ScrollBar::HandleSliderPaged(StringHash eventType, VariantMap& eventData)
+{
+    using namespace SliderPaged;
+
+    // Synthesize hover event to the forward/back buttons
+    if (eventData[P_OFFSET].GetInt() < 0)
+        backButton_->OnHover(IntVector2::ZERO, backButton_->ElementToScreen(IntVector2::ZERO), 0, 0, 0);
+    else
+        forwardButton_->OnHover(IntVector2::ZERO, forwardButton_->ElementToScreen(IntVector2::ZERO), 0, 0, 0);
+
+    // Synthesize click / release events to the buttons
+    if (eventData[P_PRESSED].GetBool())
+    {
+        if (eventData[P_OFFSET].GetInt() < 0)
+            backButton_->OnClickBegin(IntVector2::ZERO, backButton_->ElementToScreen(IntVector2::ZERO),
+                MOUSEB_LEFT, MOUSEB_LEFT, 0, 0);
+        else
+            forwardButton_->OnClickBegin(IntVector2::ZERO, forwardButton_->ElementToScreen(IntVector2::ZERO),
+                MOUSEB_LEFT, MOUSEB_LEFT, 0, 0);
+    }
+    else
+    {
+        if (eventData[P_OFFSET].GetInt() < 0)
+            backButton_->OnClickEnd(IntVector2::ZERO, backButton_->ElementToScreen(IntVector2::ZERO),
+                MOUSEB_LEFT, 0, 0, 0, backButton_);
+        else
+            forwardButton_->OnClickEnd(IntVector2::ZERO, forwardButton_->ElementToScreen(IntVector2::ZERO),
+                MOUSEB_LEFT, 0, 0, 0, forwardButton_);
+    }
+}
+
+}
+
+}

+ 137 - 0
Source/Atomic/UI/SystemUI/ScrollBar.h

@@ -0,0 +1,137 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "UIElement.h"
+
+namespace Atomic
+{
+
+
+namespace SystemUI
+{
+
+class Button;
+class Slider;
+
+/// Scroll bar %UI element with forward and back buttons.
+class ATOMIC_API ScrollBar : public UIElement
+{
+    OBJECT(ScrollBar);
+
+public:
+    /// Construct.
+    ScrollBar(Context* context);
+    /// Destruct.
+    virtual ~ScrollBar();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Apply attribute changes that can not be applied immediately.
+    virtual void ApplyAttributes();
+    /// React to resize.
+    virtual void OnResize();
+    /// React to editable status change.
+    virtual void OnSetEditable();
+
+    /// Set orientation type.
+    void SetOrientation(Orientation orientation);
+    /// Set slider range maximum value (minimum value is always 0.)
+    void SetRange(float range);
+    /// Set slider current value.
+    void SetValue(float value);
+    /// Change slider current value by a delta.
+    void ChangeValue(float delta);
+    /// Set button scroll step.
+    void SetScrollStep(float step);
+    /// Set button step factor, can be used to adjust the step for constant pixel size.
+    void SetStepFactor(float factor);
+    /// Scroll back one step.
+    void StepBack();
+    /// Scroll forward one step.
+    void StepForward();
+
+    /// Return scrollbar orientation.
+    Orientation GetOrientation() const;
+    /// Return slider range.
+    float GetRange() const;
+    /// Return slider current value.
+    float GetValue() const;
+
+    /// Return button scroll step.
+    float GetScrollStep() const { return scrollStep_; }
+
+    /// Return button step factor.
+    float GetStepFactor() const { return stepFactor_; }
+
+    /// Return scroll step multiplied by factor.
+    float GetEffectiveScrollStep() const;
+
+    /// Return back button element.
+    Button* GetBackButton() const { return backButton_; }
+
+    /// Return forward button element.
+    Button* GetForwardButton() const { return forwardButton_; }
+
+    /// Return slider element.
+    Slider* GetSlider() const { return slider_; }
+
+protected:
+    /// Filter implicit attributes in serialization process.
+    virtual bool FilterImplicitAttributes(XMLElement& dest) const;
+    /// Filter implicit attributes in serialization process for internal button.
+    bool FilterButtonImplicitAttributes(XMLElement& dest, const String& name) const;
+
+    /// Back button.
+    SharedPtr<Button> backButton_;
+    /// Forward button.
+    SharedPtr<Button> forwardButton_;
+    /// Slider.
+    SharedPtr<Slider> slider_;
+    /// Scroll step.
+    float scrollStep_;
+    /// Step factor.
+    float stepFactor_;
+    /// Left button image rect.
+    IntRect leftRect_;
+    /// Right button image rect.
+    IntRect rightRect_;
+    /// Up button image rect.
+    IntRect upRect_;
+    /// Down button image rect.
+    IntRect downRect_;
+
+private:
+    /// Handle back button pressed.
+    void HandleBackButtonPressed(StringHash eventType, VariantMap& eventData);
+    /// Handle forward button pressed.
+    void HandleForwardButtonPressed(StringHash eventType, VariantMap& eventData);
+    /// Handle slider movement.
+    void HandleSliderChanged(StringHash eventType, VariantMap& eventData);
+    /// Handle slider touch and click on "paging" area.
+    void HandleSliderPaged(StringHash eventType, VariantMap& eventData);
+};
+
+}
+
+}

+ 709 - 0
Source/Atomic/UI/SystemUI/ScrollView.cpp

@@ -0,0 +1,709 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Input/InputEvents.h"
+#include "BorderImage.h"
+#include "ScrollBar.h"
+#include "ScrollView.h"
+#include "Slider.h"
+#include "SystemUI.h"
+#include "SystemUIEvents.h"
+
+#include "../../DebugNew.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+static const float STEP_FACTOR = 300.0f;
+
+extern const char* UI_CATEGORY;
+
+ScrollView::ScrollView(Context* context) :
+    UIElement(context),
+    viewPosition_(IntVector2::ZERO),
+    viewSize_(IntVector2::ZERO),
+    viewPositionAttr_(IntVector2::ZERO),
+    touchScrollSpeed_(Vector2::ZERO),
+    touchScrollSpeedMax_(Vector2::ZERO),
+    pageStep_(1.0f),
+    scrollBarsAutoVisible_(true),
+    ignoreEvents_(false),
+    resizeContentWidth_(false),
+    scrollDeceleration_(30.0f),
+    scrollSnapEpsilon_(M_EPSILON),
+    scrollTouchDown_(false),
+    barScrolling_(false),
+    autoDisableChildren_(false),
+    scrollChildrenDisable_(false),
+    touchDistanceSum_(0.0f),
+    autoDisableThreshold_(25.0f)
+{
+    clipChildren_ = true;
+    SetEnabled(true);
+    focusMode_ = FM_FOCUSABLE_DEFOCUSABLE;
+
+    horizontalScrollBar_ = CreateChild<ScrollBar>("SV_HorizontalScrollBar");
+    horizontalScrollBar_->SetInternal(true);
+    horizontalScrollBar_->SetAlignment(HA_LEFT, VA_BOTTOM);
+    horizontalScrollBar_->SetOrientation(O_HORIZONTAL);
+    verticalScrollBar_ = CreateChild<ScrollBar>("SV_VerticalScrollBar");
+    verticalScrollBar_->SetInternal(true);
+    verticalScrollBar_->SetAlignment(HA_RIGHT, VA_TOP);
+    verticalScrollBar_->SetOrientation(O_VERTICAL);
+    scrollPanel_ = CreateChild<BorderImage>("SV_ScrollPanel");
+    scrollPanel_->SetInternal(true);
+    scrollPanel_->SetEnabled(true);
+    scrollPanel_->SetClipChildren(true);
+
+    SubscribeToEvent(horizontalScrollBar_, E_SCROLLBARCHANGED, HANDLER(ScrollView, HandleScrollBarChanged));
+    SubscribeToEvent(horizontalScrollBar_, E_VISIBLECHANGED, HANDLER(ScrollView, HandleScrollBarVisibleChanged));
+    SubscribeToEvent(verticalScrollBar_, E_SCROLLBARCHANGED, HANDLER(ScrollView, HandleScrollBarChanged));
+    SubscribeToEvent(verticalScrollBar_, E_VISIBLECHANGED, HANDLER(ScrollView, HandleScrollBarVisibleChanged));
+    SubscribeToEvent(E_TOUCHMOVE, HANDLER(ScrollView, HandleTouchMove));
+    SubscribeToEvent(E_TOUCHBEGIN, HANDLER(ScrollView, HandleTouchMove));
+    SubscribeToEvent(E_TOUCHEND, HANDLER(ScrollView, HandleTouchMove));
+
+}
+
+ScrollView::~ScrollView()
+{
+}
+
+void ScrollView::RegisterObject(Context* context)
+{
+    context->RegisterFactory<ScrollView>(UI_CATEGORY);
+
+    COPY_BASE_ATTRIBUTES(UIElement);
+    UPDATE_ATTRIBUTE_DEFAULT_VALUE("Clip Children", true);
+    UPDATE_ATTRIBUTE_DEFAULT_VALUE("Is Enabled", true);
+    UPDATE_ATTRIBUTE_DEFAULT_VALUE("Focus Mode", FM_FOCUSABLE_DEFOCUSABLE);
+    ACCESSOR_ATTRIBUTE("View Position", GetViewPosition, SetViewPositionAttr, IntVector2, IntVector2::ZERO, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Scroll Step", GetScrollStep, SetScrollStep, float, 0.1f, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Page Step", GetPageStep, SetPageStep, float, 1.0f, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Auto Show/Hide Scrollbars", GetScrollBarsAutoVisible, SetScrollBarsAutoVisible, bool, true, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Scroll Deceleration", GetScrollDeceleration, SetScrollDeceleration, float, 30.0f, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Scroll Snap Epsilon", GetScrollSnapEpsilon, SetScrollSnapEpsilon, float, 1.0f, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Auto Disable Children", GetAutoDisableChildren, SetAutoDisableChildren, bool, false, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Auto Disable Threshold", GetAutoDisableThreshold, SetAutoDisableThreshold, float, 25.0f, AM_FILE);
+}
+
+void ScrollView::Update(float timeStep)
+{
+    // Update touch scrolling here if necessary
+    if (touchScrollSpeed_ == Vector2::ZERO && touchScrollSpeedMax_ == Vector2::ZERO && !barScrolling_)
+        return;
+
+    // Check if we should not scroll:
+    // - ScrollView is not visible, is not enabled, or doesn't have focus
+    // - The element being dragged is not a child of the ScrollView, or is one of our scrollbars
+    if (!IsVisible() || !IsEnabled() || !HasFocus())
+    {
+        touchScrollSpeed_ = Vector2::ZERO;
+        touchScrollSpeedMax_ = Vector2::ZERO;
+        return;
+    }
+
+    if (GetSubsystem<SystemUI>()->IsDragging())
+    {
+        Vector<UIElement*> dragElements = GetSubsystem<SystemUI>()->GetDragElements();
+
+        for (unsigned i = 0; i < dragElements.Size(); i++)
+        {
+            UIElement* dragElement = dragElements[i];
+            int dragButtons = dragElement->GetDragButtonCombo();
+
+            if (dragButtons != MOUSEB_LEFT)
+                continue;
+
+            UIElement* dragParent = dragElement->GetParent();
+            bool dragElementIsChild = false;
+
+            while (dragParent)
+            {
+                if (dragParent == this)
+                {
+                    dragElementIsChild = true;
+                    break;
+                }
+                dragParent = dragParent->GetParent();
+            }
+
+            if (!dragElementIsChild || dragElement == horizontalScrollBar_->GetSlider() ||
+                dragElement == verticalScrollBar_->GetSlider())
+            {
+                touchScrollSpeed_ = Vector2::ZERO;
+                touchScrollSpeedMax_ = Vector2::ZERO;
+                return;
+            }
+        }
+    }
+
+    // Update view position
+    IntVector2 newPosition = viewPosition_;
+    newPosition.x_ += (int)touchScrollSpeed_.x_;
+    newPosition.y_ += (int)touchScrollSpeed_.y_;
+    SetViewPosition(newPosition);
+
+    // Smooth deceleration
+    ScrollSmooth(timeStep);
+}
+
+void ScrollView::ApplyAttributes()
+{
+    UIElement::ApplyAttributes();
+
+    // Set the scrollbar orientations again and perform size update now that the style is known
+    horizontalScrollBar_->SetOrientation(O_HORIZONTAL);
+    verticalScrollBar_->SetOrientation(O_VERTICAL);
+
+    // If the scroll panel has a child, it should be the content element, which has some special handling
+    if (scrollPanel_->GetNumChildren())
+        SetContentElement(scrollPanel_->GetChild(0));
+
+    OnResize();
+
+    // Reapply view position with proper content element and size
+    SetViewPosition(viewPositionAttr_);
+}
+
+void ScrollView::OnWheel(int delta, int buttons, int qualifiers)
+{
+    if (delta > 0)
+        verticalScrollBar_->StepBack();
+    if (delta < 0)
+        verticalScrollBar_->StepForward();
+}
+
+void ScrollView::OnKey(int key, int buttons, int qualifiers)
+{
+    switch (key)
+    {
+    case KEY_LEFT:
+        if (horizontalScrollBar_->IsVisible())
+        {
+            if (qualifiers & QUAL_CTRL)
+                horizontalScrollBar_->SetValue(0.0f);
+            else
+                horizontalScrollBar_->StepBack();
+        }
+        break;
+
+    case KEY_RIGHT:
+        if (horizontalScrollBar_->IsVisible())
+        {
+            if (qualifiers & QUAL_CTRL)
+                horizontalScrollBar_->SetValue(horizontalScrollBar_->GetRange());
+            else
+                horizontalScrollBar_->StepForward();
+        }
+        break;
+
+    case KEY_HOME:
+        qualifiers |= QUAL_CTRL;
+        // Fallthru
+
+    case KEY_UP:
+        if (verticalScrollBar_->IsVisible())
+        {
+            if (qualifiers & QUAL_CTRL)
+                verticalScrollBar_->SetValue(0.0f);
+            else
+                verticalScrollBar_->StepBack();
+        }
+        break;
+
+    case KEY_END:
+        qualifiers |= QUAL_CTRL;
+        // Fallthru
+
+    case KEY_DOWN:
+        if (verticalScrollBar_->IsVisible())
+        {
+            if (qualifiers & QUAL_CTRL)
+                verticalScrollBar_->SetValue(verticalScrollBar_->GetRange());
+            else
+                verticalScrollBar_->StepForward();
+        }
+        break;
+
+    case KEY_PAGEUP:
+        if (verticalScrollBar_->IsVisible())
+            verticalScrollBar_->ChangeValue(-pageStep_);
+        break;
+
+    case KEY_PAGEDOWN:
+        if (verticalScrollBar_->IsVisible())
+            verticalScrollBar_->ChangeValue(pageStep_);
+        break;
+
+    default: break;
+    }
+}
+
+void ScrollView::OnResize()
+{
+    UpdatePanelSize();
+    UpdateViewSize();
+
+    // If scrollbar auto visibility is enabled, check whether scrollbars should be visible.
+    // This may force another update of the panel size
+    if (scrollBarsAutoVisible_)
+    {
+        ignoreEvents_ = true;
+        horizontalScrollBar_->SetVisible(horizontalScrollBar_->GetRange() > M_EPSILON);
+        verticalScrollBar_->SetVisible(verticalScrollBar_->GetRange() > M_EPSILON);
+        ignoreEvents_ = false;
+
+        UpdatePanelSize();
+    }
+}
+
+void ScrollView::SetContentElement(UIElement* element)
+{
+    if (element == contentElement_)
+        return;
+
+    if (contentElement_)
+    {
+        scrollPanel_->RemoveChild(contentElement_);
+        UnsubscribeFromEvent(contentElement_, E_RESIZED);
+    }
+    contentElement_ = element;
+    if (contentElement_)
+    {
+        scrollPanel_->AddChild(contentElement_);
+        SubscribeToEvent(contentElement_, E_RESIZED, HANDLER(ScrollView, HandleElementResized));
+    }
+
+    OnResize();
+}
+
+void ScrollView::SetViewPosition(const IntVector2& position)
+{
+    UpdateView(position);
+    UpdateScrollBars();
+}
+
+void ScrollView::SetViewPosition(int x, int y)
+{
+    SetViewPosition(IntVector2(x, y));
+}
+
+void ScrollView::SetScrollBarsVisible(bool horizontal, bool vertical)
+{
+    scrollBarsAutoVisible_ = false;
+    horizontalScrollBar_->SetVisible(horizontal);
+    verticalScrollBar_->SetVisible(vertical);
+}
+
+void ScrollView::SetScrollBarsAutoVisible(bool enable)
+{
+    if (enable != scrollBarsAutoVisible_)
+    {
+        scrollBarsAutoVisible_ = enable;
+        // Check whether scrollbars should be visible now
+        if (enable)
+            OnResize();
+        else
+        {
+            horizontalScrollBar_->SetVisible(true);
+            verticalScrollBar_->SetVisible(true);
+        }
+    }
+}
+
+void ScrollView::SetScrollStep(float step)
+{
+    horizontalScrollBar_->SetScrollStep(step);
+    verticalScrollBar_->SetScrollStep(step);
+}
+
+void ScrollView::SetPageStep(float step)
+{
+    pageStep_ = Max(step, 0.0f);
+}
+
+float ScrollView::GetScrollStep() const
+{
+    return horizontalScrollBar_->GetScrollStep();
+}
+
+void ScrollView::SetViewPositionAttr(const IntVector2& value)
+{
+    viewPositionAttr_ = value;
+    SetViewPosition(value);
+}
+
+bool ScrollView::FilterImplicitAttributes(XMLElement& dest) const
+{
+    if (!UIElement::FilterImplicitAttributes(dest))
+        return false;
+
+    XMLElement childElem = dest.GetChild("element");
+    if (!FilterScrollBarImplicitAttributes(childElem, "SV_HorizontalScrollBar"))
+        return false;
+    if (!RemoveChildXML(childElem, "Vert Alignment", "Bottom"))
+        return false;
+
+    childElem = childElem.GetNext("element");
+    if (!FilterScrollBarImplicitAttributes(childElem, "SV_VerticalScrollBar"))
+        return false;
+    if (!RemoveChildXML(childElem, "Horiz Alignment", "Right"))
+        return false;
+
+    childElem = childElem.GetNext("element");
+    if (!childElem)
+        return false;
+    if (!RemoveChildXML(childElem, "Name", "SV_ScrollPanel"))
+        return false;
+    if (!RemoveChildXML(childElem, "Is Enabled", "true"))
+        return false;
+    if (!RemoveChildXML(childElem, "Clip Children", "true"))
+        return false;
+    if (!RemoveChildXML(childElem, "Size"))
+        return false;
+
+    return true;
+}
+
+bool ScrollView::FilterScrollBarImplicitAttributes(XMLElement& dest, const String& name) const
+{
+    if (!dest)
+        return false;
+    if (!RemoveChildXML(dest, "Name", name))
+        return false;
+    if (!RemoveChildXML(dest, "Orientation"))
+        return false;
+    if (!RemoveChildXML(dest, "Range"))
+        return false;
+    if (!RemoveChildXML(dest, "Step Factor"))
+        return false;
+    if (scrollBarsAutoVisible_)
+    {
+        if (!RemoveChildXML(dest, "Is Visible"))
+            return false;
+    }
+
+    return true;
+}
+
+void ScrollView::UpdatePanelSize()
+{
+    // Ignore events in case content element resizes itself along with the panel
+    // (content element resize triggers our OnResize(), so it could lead to infinite recursion)
+    ignoreEvents_ = true;
+
+    IntVector2 panelSize = GetSize();
+    if (verticalScrollBar_->IsVisible())
+        panelSize.x_ -= verticalScrollBar_->GetWidth();
+    if (horizontalScrollBar_->IsVisible())
+        panelSize.y_ -= horizontalScrollBar_->GetHeight();
+
+    scrollPanel_->SetSize(panelSize);
+    horizontalScrollBar_->SetWidth(scrollPanel_->GetWidth());
+    verticalScrollBar_->SetHeight(scrollPanel_->GetHeight());
+
+    if (resizeContentWidth_ && contentElement_)
+    {
+        IntRect panelBorder = scrollPanel_->GetClipBorder();
+        contentElement_->SetWidth(scrollPanel_->GetWidth() - panelBorder.left_ - panelBorder.right_);
+        UpdateViewSize();
+    }
+
+    ignoreEvents_ = false;
+}
+
+void ScrollView::UpdateViewSize()
+{
+    IntVector2 size(IntVector2::ZERO);
+    if (contentElement_)
+        size = contentElement_->GetSize();
+    IntRect panelBorder = scrollPanel_->GetClipBorder();
+
+    viewSize_.x_ = Max(size.x_, scrollPanel_->GetWidth() - panelBorder.left_ - panelBorder.right_);
+    viewSize_.y_ = Max(size.y_, scrollPanel_->GetHeight() - panelBorder.top_ - panelBorder.bottom_);
+
+    UpdateView(viewPosition_);
+    UpdateScrollBars();
+}
+
+void ScrollView::UpdateScrollBars()
+{
+    ignoreEvents_ = true;
+
+    IntVector2 size = scrollPanel_->GetSize();
+    IntRect panelBorder = scrollPanel_->GetClipBorder();
+    size.x_ -= panelBorder.left_ + panelBorder.right_;
+    size.y_ -= panelBorder.top_ + panelBorder.bottom_;
+
+    if (size.x_ > 0 && viewSize_.x_ > 0)
+    {
+        float range = (float)viewSize_.x_ / (float)size.x_ - 1.0f;
+        horizontalScrollBar_->SetRange(range);
+        horizontalScrollBar_->SetValue((float)viewPosition_.x_ / (float)size.x_);
+        horizontalScrollBar_->SetStepFactor(STEP_FACTOR / (float)size.x_);
+    }
+    if (size.y_ > 0 && viewSize_.y_ > 0)
+    {
+        float range = (float)viewSize_.y_ / (float)size.y_ - 1.0f;
+        verticalScrollBar_->SetRange(range);
+        verticalScrollBar_->SetValue((float)viewPosition_.y_ / (float)size.y_);
+        verticalScrollBar_->SetStepFactor(STEP_FACTOR / (float)size.y_);
+    }
+
+    ignoreEvents_ = false;
+}
+
+void ScrollView::UpdateView(const IntVector2& position)
+{
+    IntVector2 oldPosition = viewPosition_;
+    IntRect panelBorder = scrollPanel_->GetClipBorder();
+    IntVector2 panelSize(scrollPanel_->GetWidth() - panelBorder.left_ - panelBorder.right_,
+        scrollPanel_->GetHeight() - panelBorder.top_ - panelBorder.bottom_);
+
+    viewPosition_.x_ = Clamp(position.x_, 0, viewSize_.x_ - panelSize.x_);
+    viewPosition_.y_ = Clamp(position.y_, 0, viewSize_.y_ - panelSize.y_);
+    scrollPanel_->SetChildOffset(IntVector2(-viewPosition_.x_ + panelBorder.left_, -viewPosition_.y_ + panelBorder.top_));
+
+    if (viewPosition_ != oldPosition)
+    {
+        using namespace ViewChanged;
+
+        VariantMap& eventData = GetEventDataMap();
+        eventData[P_ELEMENT] = this;
+        eventData[P_X] = viewPosition_.x_;
+        eventData[P_Y] = viewPosition_.y_;
+        SendEvent(E_VIEWCHANGED, eventData);
+    }
+}
+
+void ScrollView::HandleScrollBarChanged(StringHash eventType, VariantMap& eventData)
+{
+    if (!ignoreEvents_)
+    {
+        IntVector2 size = scrollPanel_->GetSize();
+        IntRect panelBorder = scrollPanel_->GetClipBorder();
+        size.x_ -= panelBorder.left_ + panelBorder.right_;
+        size.y_ -= panelBorder.top_ + panelBorder.bottom_;
+
+        UpdateView(IntVector2(
+            (int)(horizontalScrollBar_->GetValue() * (float)size.x_),
+            (int)(verticalScrollBar_->GetValue() * (float)size.y_)
+        ));
+    }
+}
+
+void ScrollView::HandleScrollBarVisibleChanged(StringHash eventType, VariantMap& eventData)
+{
+    // Need to recalculate panel size when scrollbar visibility changes
+    if (!ignoreEvents_)
+        OnResize();
+}
+
+void ScrollView::HandleElementResized(StringHash eventType, VariantMap& eventData)
+{
+    if (!ignoreEvents_)
+        OnResize();
+}
+
+void ScrollView::HandleTouchMove(StringHash eventType, VariantMap& eventData)
+{
+    using namespace TouchMove;
+
+    if (eventType == E_TOUCHMOVE && !barScrolling_)
+    {
+        scrollTouchDown_ = true;
+        // Take new scrolling speed if it's faster than the current accumulated value
+        float dX = (float)-eventData[P_DX].GetInt();
+        float dY = (float)-eventData[P_DY].GetInt();
+
+        if (Abs(dX) > Abs(touchScrollSpeed_.x_))
+            touchScrollSpeed_.x_ = dX;
+        if (Abs(dY) > Abs(touchScrollSpeed_.y_))
+            touchScrollSpeed_.y_ = dY;
+
+        // Auto disable children
+        touchDistanceSum_ += dX * dX + dY * dY;
+        if (autoDisableChildren_ && touchDistanceSum_ >= autoDisableThreshold_)
+        {
+            if (visible_ && !scrollChildrenDisable_)
+            {
+                scrollChildrenDisable_ = true;
+                scrollPanel_->SetDeepEnabled(false);
+            }
+        }
+
+        touchScrollSpeedMax_.x_ = dX;
+        touchScrollSpeedMax_.y_ = dY;
+    }
+    else if (eventType == E_TOUCHBEGIN)
+    {
+        int X = eventData[P_X].GetInt();
+        int Y = eventData[P_Y].GetInt();
+        IntVector2 pos = IntVector2(X, Y);
+
+        // Prevent conflict between touch scroll and scrollbar scroll
+        if (horizontalScrollBar_->IsVisible() && horizontalScrollBar_->IsInsideCombined(pos, true))
+            barScrolling_ = true;
+
+        if (verticalScrollBar_->IsVisible() && verticalScrollBar_->IsInsideCombined(pos, true))
+            barScrolling_ = true;
+
+        // Stop the smooth scrolling
+        touchScrollSpeed_ = Vector2::ZERO;
+        touchScrollSpeedMax_ = Vector2::ZERO;
+    }
+    else if (eventType == E_TOUCHEND)
+    {
+        // 'Flick' action
+        // Release any auto disabled children
+        if (scrollChildrenDisable_)
+        {
+            touchDistanceSum_ = 0.0f;
+            scrollChildrenDisable_ = false;
+            scrollPanel_->ResetDeepEnabled();
+        }
+
+        barScrolling_ = false;
+        scrollTouchDown_ = false;
+        if (Abs(touchScrollSpeedMax_.x_) > scrollSnapEpsilon_)
+            touchScrollSpeed_.x_ = touchScrollSpeedMax_.x_;
+        else
+            touchScrollSpeed_.x_ = 0;
+
+        if (Abs(touchScrollSpeedMax_.y_) > scrollSnapEpsilon_)
+            touchScrollSpeed_.y_ = touchScrollSpeedMax_.y_;
+        else
+            touchScrollSpeed_.y_ = 0;
+        touchScrollSpeedMax_ = Vector2::ZERO;
+    }
+}
+
+void ScrollView::ScrollSmooth(float timeStep)
+{
+    // Decay the momentum
+    if (touchScrollSpeedMax_.x_ >= scrollSnapEpsilon_)
+    {
+        touchScrollSpeedMax_.x_ -= scrollDeceleration_ * timeStep;
+        touchScrollSpeedMax_.x_ = touchScrollSpeedMax_.x_ > 0 ? touchScrollSpeedMax_.x_ : 0;
+    }
+    else if (touchScrollSpeedMax_.x_ <= -scrollSnapEpsilon_)
+    {
+        touchScrollSpeedMax_.x_ += scrollDeceleration_ * timeStep;
+        touchScrollSpeedMax_.x_ = touchScrollSpeedMax_.x_ < 0 ? touchScrollSpeedMax_.x_ : 0;
+    }
+    else
+        touchScrollSpeedMax_.x_ = 0;
+
+    if (touchScrollSpeedMax_.y_ >= scrollSnapEpsilon_)
+    {
+        touchScrollSpeedMax_.y_ -= scrollDeceleration_ * timeStep;
+        touchScrollSpeedMax_.y_ = touchScrollSpeedMax_.y_ > 0 ? touchScrollSpeedMax_.y_ : 0;
+    }
+    else if (touchScrollSpeedMax_.y_ <= -scrollSnapEpsilon_)
+    {
+        touchScrollSpeedMax_.y_ += scrollDeceleration_ * timeStep;
+        touchScrollSpeedMax_.y_ = touchScrollSpeedMax_.y_ < 0 ? touchScrollSpeedMax_.y_ : 0;
+    }
+    else
+        touchScrollSpeedMax_.y_ = 0;
+
+    // Control vs flick
+    if (scrollTouchDown_)
+    {
+        // Finger is held down: control = instant stop
+        touchScrollSpeed_ = Vector2::ZERO;
+    }
+    else
+    {
+        // Finger is released: flick = smooth deceleration
+        if (touchScrollSpeed_.x_ >= scrollSnapEpsilon_)
+        {
+            touchScrollSpeed_.x_ -= scrollDeceleration_ * timeStep;
+            if (touchScrollSpeed_.x_ < 0)
+            {
+                touchScrollSpeed_.x_ = 0;
+            }
+
+            if (horizontalScrollBar_->GetValue() >= horizontalScrollBar_->GetRange() - M_EPSILON)
+            {
+                // Stop movement when we reach end of scroll
+                touchScrollSpeed_.x_ = 0;
+            }
+        }
+        else if (touchScrollSpeed_.x_ < -scrollSnapEpsilon_)
+        {
+            touchScrollSpeed_.x_ += scrollDeceleration_ * timeStep;
+            if (touchScrollSpeed_.x_ > 0)
+            {
+                touchScrollSpeed_.x_ = 0;
+            }
+
+            if (horizontalScrollBar_->GetValue() <= M_EPSILON)
+            {
+                // Stop movement when we reach end of scroll
+                touchScrollSpeed_.x_ = 0;
+            }
+        }
+        else
+            touchScrollSpeed_.x_ = 0;
+
+        if (touchScrollSpeed_.y_ >= scrollSnapEpsilon_)
+        {
+            touchScrollSpeed_.y_ -= scrollDeceleration_ * timeStep;
+            if (touchScrollSpeed_.y_ < 0)
+            {
+                touchScrollSpeed_.y_ = 0;
+            }
+
+            if (verticalScrollBar_->GetValue() >= verticalScrollBar_->GetRange() - M_EPSILON)
+            {
+                // Stop movement when we reach end of scroll
+                touchScrollSpeed_.y_ = 0;
+            }
+        }
+        else if (touchScrollSpeed_.y_ < -scrollSnapEpsilon_)
+        {
+            touchScrollSpeed_.y_ += scrollDeceleration_ * timeStep;
+            if (touchScrollSpeed_.y_ > 0)
+            {
+                touchScrollSpeed_.y_ = 0;
+            }
+
+            if (verticalScrollBar_->GetValue() <= M_EPSILON)
+            {
+                // Stop movement when we reach end of scroll
+                touchScrollSpeed_.y_ = 0;
+            }
+        }
+        else
+            touchScrollSpeed_.y_ = 0;
+    }
+}
+
+}
+
+}

+ 199 - 0
Source/Atomic/UI/SystemUI/ScrollView.h

@@ -0,0 +1,199 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "UIElement.h"
+
+namespace Atomic
+{
+
+
+namespace SystemUI
+{
+
+class BorderImage;
+class ScrollBar;
+
+/// Scrollable %UI element for showing a (possibly large) child element.
+class ATOMIC_API ScrollView : public UIElement
+{
+    OBJECT(ScrollView);
+
+public:
+    /// Construct.
+    ScrollView(Context* context);
+    /// Destruct.
+    virtual ~ScrollView();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Perform UI element update.
+    virtual void Update(float timeStep);
+    /// Apply attribute changes that can not be applied immediately.
+    virtual void ApplyAttributes();
+    /// React to mouse wheel.
+    virtual void OnWheel(int delta, int buttons, int qualifiers);
+    /// React to a key press.
+    virtual void OnKey(int key, int buttons, int qualifiers);
+    /// React to resize.
+    virtual void OnResize();
+
+    /// Set content element.
+    void SetContentElement(UIElement* element);
+    /// Set view offset from the top-left corner.
+    void SetViewPosition(const IntVector2& position);
+    /// Set view offset from the top-left corner.
+    void SetViewPosition(int x, int y);
+    /// Set scrollbars' visibility manually. Disables scrollbar autoshow/hide.
+    void SetScrollBarsVisible(bool horizontal, bool vertical);
+    /// Set whether to automatically show/hide scrollbars. Default true.
+    void SetScrollBarsAutoVisible(bool enable);
+    /// Set arrow key scroll step. Also sets it on the scrollbars.
+    void SetScrollStep(float step);
+    /// Set arrow key page step.
+    void SetPageStep(float step);
+
+    /// Set scroll deceleration.
+    void SetScrollDeceleration(float deceleration) { scrollDeceleration_ = deceleration; }
+
+    /// Set scroll snap epsilon
+    void SetScrollSnapEpsilon(float snap) { scrollSnapEpsilon_ = snap; }
+
+    /// Set whether child elements should be disabled while touch scrolling.
+    void SetAutoDisableChildren(bool disable) { autoDisableChildren_ = disable; };
+
+    /// Set how much touch movement is needed to trigger child element disabling.
+    void SetAutoDisableThreshold(float amount) { autoDisableThreshold_ = amount; };
+
+    /// Return view offset from the top-left corner.
+    const IntVector2& GetViewPosition() const { return viewPosition_; }
+
+    /// Return content element.
+    UIElement* GetContentElement() const { return contentElement_; }
+
+    /// Return horizontal scroll bar.
+    ScrollBar* GetHorizontalScrollBar() const { return horizontalScrollBar_; }
+
+    /// Return vertical scroll bar.
+    ScrollBar* GetVerticalScrollBar() const { return verticalScrollBar_; }
+
+    /// Return scroll panel.
+    BorderImage* GetScrollPanel() const { return scrollPanel_; }
+
+    /// Return whether scrollbars are automatically shown/hidden.
+    bool GetScrollBarsAutoVisible() const { return scrollBarsAutoVisible_; }
+
+    /// Return arrow key scroll step.
+    float GetScrollStep() const;
+
+    /// Return arrow key page step.
+    float GetPageStep() const { return pageStep_; }
+
+    /// Return scroll deceleration.
+    float GetScrollDeceleration() const { return scrollDeceleration_; }
+
+    /// Return scroll snap epsilon
+    float GetScrollSnapEpsilon() const { return scrollSnapEpsilon_; }
+
+    /// Return whether child element will be disabled while touch scrolling.
+    bool GetAutoDisableChildren() const { return autoDisableChildren_; }
+
+    /// Return how much touch movement is needed to trigger child element disabling.
+    float GetAutoDisableThreshold() const { return autoDisableThreshold_; }
+
+    /// Set view position attribute.
+    void SetViewPositionAttr(const IntVector2& value);
+
+protected:
+    /// Filter implicit attributes in serialization process.
+    virtual bool FilterImplicitAttributes(XMLElement& dest) const;
+    /// Filter implicit attributes in serialization process for internal scroll bar.
+    bool FilterScrollBarImplicitAttributes(XMLElement& dest, const String& name) const;
+    /// Resize panel based on scrollbar visibility.
+    void UpdatePanelSize();
+    /// Recalculate view size, validate view position and update scrollbars.
+    void UpdateViewSize();
+    /// Update the scrollbars' ranges and positions.
+    void UpdateScrollBars();
+    /// Limit and update the view with a new position.
+    void UpdateView(const IntVector2& position);
+
+    /// Content element.
+    SharedPtr<UIElement> contentElement_;
+    /// Horizontal scroll bar.
+    SharedPtr<ScrollBar> horizontalScrollBar_;
+    /// Vertical scroll bar.
+    SharedPtr<ScrollBar> verticalScrollBar_;
+    /// Scroll panel element.
+    SharedPtr<BorderImage> scrollPanel_;
+    /// Current view offset from the top-left corner.
+    IntVector2 viewPosition_;
+    /// Total view size.
+    IntVector2 viewSize_;
+    /// View offset attribute.
+    IntVector2 viewPositionAttr_;
+    /// Accumulated touch scroll speed.
+    Vector2 touchScrollSpeed_;
+    /// Max touch scroll speed.
+    Vector2 touchScrollSpeedMax_;
+    /// Arrow key page step.
+    float pageStep_;
+    /// Automatically show/hide scrollbars flag.
+    bool scrollBarsAutoVisible_;
+    /// Ignore scrollbar events flag. Used to prevent possible endless loop when resizing.
+    bool ignoreEvents_;
+    /// Resize content widget width to match panel. Internal flag, used by the ListView class.
+    bool resizeContentWidth_;
+    /// Scroll deceleration
+    float scrollDeceleration_;
+    /// Scroll snap epsilon
+    float scrollSnapEpsilon_;
+    /// Used to trigger scroll smoothing when false;
+    bool scrollTouchDown_;
+    /// Used to prevent touch scroll - scroll bar conflict
+    bool barScrolling_;
+    /// Used to determine if child elements should be disabled while touch scrolling, to prevent their trigger.
+    bool autoDisableChildren_;
+    /// Used to determine if children have been disabled.
+    bool scrollChildrenDisable_;
+    /// Distance moved with touch scrolling
+    float touchDistanceSum_;
+    /// Threshold to trigger auto disable children
+    float autoDisableThreshold_;
+
+private:
+    /// Handle scrollbar value changed.
+    void HandleScrollBarChanged(StringHash eventType, VariantMap& eventData);
+    /// Handle scrollbar visibility changed.
+    void HandleScrollBarVisibleChanged(StringHash eventType, VariantMap& eventData);
+    /// Handle content element resized.
+    void HandleElementResized(StringHash eventType, VariantMap& eventData);
+    /// Handle touch move event for scrolling.
+    void HandleTouchMove(StringHash eventType, VariantMap& eventData);
+    /// Handle the scroll smoothing.
+    void ScrollSmooth(float timeStep);
+};
+
+}
+
+}

+ 287 - 0
Source/Atomic/UI/SystemUI/Slider.cpp

@@ -0,0 +1,287 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Input/InputEvents.h"
+#include "../../IO/Log.h"
+#include "Slider.h"
+#include "SystemUIEvents.h"
+
+#include "../../DebugNew.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+const char* orientations[] =
+{
+    "Horizontal",
+    "Vertical",
+    0
+};
+
+extern const char* UI_CATEGORY;
+
+Slider::Slider(Context* context) :
+    BorderImage(context),
+    orientation_(O_HORIZONTAL),
+    range_(1.0f),
+    value_(0.0f),
+    dragSlider_(false),
+    repeatRate_(0.0f)
+{
+    SetEnabled(true);
+    knob_ = CreateChild<BorderImage>("S_Knob");
+    knob_->SetInternal(true);
+
+    UpdateSlider();
+}
+
+Slider::~Slider()
+{
+}
+
+void Slider::RegisterObject(Context* context)
+{
+    context->RegisterFactory<Slider>(UI_CATEGORY);
+
+    COPY_BASE_ATTRIBUTES(BorderImage);
+    UPDATE_ATTRIBUTE_DEFAULT_VALUE("Is Enabled", true);
+    ENUM_ACCESSOR_ATTRIBUTE("Orientation", GetOrientation, SetOrientation, Orientation, orientations, O_HORIZONTAL, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Range", GetRange, SetRange, float, 1.0f, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Value", GetValue, SetValue, float, 0.0f, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Repeat Rate", GetRepeatRate, SetRepeatRate, float, 0.0f, AM_FILE);
+}
+
+void Slider::Update(float timeStep)
+{
+    if (dragSlider_)
+        hovering_ = true;
+
+    // Propagate hover effect to the slider knob
+    knob_->SetHovering(hovering_);
+    knob_->SetSelected(hovering_);
+}
+
+void Slider::OnHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
+{
+    BorderImage::OnHover(position, screenPosition, buttons, qualifiers, cursor);
+
+    // Show hover effect if inside the slider knob
+    hovering_ = knob_->IsInside(screenPosition, true);
+
+    // If not hovering on the knob, send it as page event
+    if (!hovering_)
+        Page(position, (bool)(buttons & MOUSEB_LEFT));
+}
+
+void Slider::OnClickBegin(const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers,
+    Cursor* cursor)
+{
+    selected_ = true;
+    hovering_ = knob_->IsInside(screenPosition, true);
+    if (!hovering_ && button == MOUSEB_LEFT)
+        Page(position, true);
+}
+
+void Slider::OnClickEnd(const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers,
+    Cursor* cursor, UIElement* beginElement)
+{
+    hovering_ = knob_->IsInside(screenPosition, true);
+    if (!hovering_ && button == MOUSEB_LEFT)
+        Page(position, false);
+}
+
+void Slider::OnDragBegin(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
+{
+    UIElement::OnDragBegin(position, screenPosition, buttons, qualifiers, cursor);
+
+    if (buttons == MOUSEB_LEFT)
+    {
+        dragBeginCursor_ = position;
+        dragBeginPosition_ = knob_->GetPosition();
+        dragSlider_ = knob_->IsInside(screenPosition, true);
+    }
+}
+
+void Slider::OnDragMove(const IntVector2& position, const IntVector2& screenPosition, const IntVector2& deltaPos, int buttons,
+    int qualifiers, Cursor* cursor)
+{
+    if (!editable_ || !dragSlider_ || GetSize() == knob_->GetSize())
+        return;
+
+    float newValue;
+    IntVector2 delta = position - dragBeginCursor_;
+
+    if (orientation_ == O_HORIZONTAL)
+    {
+        int newX = Clamp(dragBeginPosition_.x_ + delta.x_, 0, GetWidth() - knob_->GetWidth());
+        knob_->SetPosition(newX, 0);
+        newValue = (float)newX * range_ / (float)(GetWidth() - knob_->GetWidth());
+    }
+    else
+    {
+        int newY = Clamp(dragBeginPosition_.y_ + delta.y_, 0, GetHeight() - knob_->GetHeight());
+        knob_->SetPosition(0, newY);
+        newValue = (float)newY * range_ / (float)(GetHeight() - knob_->GetHeight());
+    }
+
+    SetValue(newValue);
+}
+
+void Slider::OnDragEnd(const IntVector2& position, const IntVector2& screenPosition, int dragButtons, int buttons, Cursor* cursor)
+{
+    UIElement::OnDragEnd(position, screenPosition, dragButtons, buttons, cursor);
+
+    if (dragButtons == MOUSEB_LEFT)
+    {
+        dragSlider_ = false;
+        selected_ = false;
+    }
+}
+
+void Slider::OnResize()
+{
+    UpdateSlider();
+}
+
+void Slider::SetOrientation(Orientation type)
+{
+    orientation_ = type;
+    UpdateSlider();
+}
+
+void Slider::SetRange(float range)
+{
+    range = Max(range, 0.0f);
+    if (range != range_)
+    {
+        range_ = range;
+        UpdateSlider();
+    }
+}
+
+void Slider::SetValue(float value)
+{
+    value = Clamp(value, 0.0f, range_);
+    if (value != value_)
+    {
+        value_ = value;
+        UpdateSlider();
+
+        using namespace SliderChanged;
+
+        VariantMap& eventData = GetEventDataMap();
+        eventData[P_ELEMENT] = this;
+        eventData[P_VALUE] = value_;
+        SendEvent(E_SLIDERCHANGED, eventData);
+    }
+}
+
+void Slider::ChangeValue(float delta)
+{
+    SetValue(value_ + delta);
+}
+
+void Slider::SetRepeatRate(float rate)
+{
+    repeatRate_ = Max(rate, 0.0f);
+}
+
+bool Slider::FilterImplicitAttributes(XMLElement& dest) const
+{
+    if (!BorderImage::FilterImplicitAttributes(dest))
+        return false;
+
+    XMLElement childElem = dest.GetChild("element");
+    if (!childElem)
+        return false;
+    if (!RemoveChildXML(childElem, "Name", "S_Knob"))
+        return false;
+    if (!RemoveChildXML(childElem, "Position"))
+        return false;
+    if (!RemoveChildXML(childElem, "Size"))
+        return false;
+
+    return true;
+}
+
+void Slider::UpdateSlider()
+{
+    const IntRect& border = knob_->GetBorder();
+
+    if (range_ > 0.0f)
+    {
+        if (orientation_ == O_HORIZONTAL)
+        {
+            int sliderLength = (int)Max((float)GetWidth() / (range_ + 1.0f), (float)(border.left_ + border.right_));
+            float sliderPos = (float)(GetWidth() - sliderLength) * value_ / range_;
+            knob_->SetSize(sliderLength, GetHeight());
+            knob_->SetPosition(Clamp((int)(sliderPos + 0.5f), 0, GetWidth() - knob_->GetWidth()), 0);
+        }
+        else
+        {
+            int sliderLength = (int)Max((float)GetHeight() / (range_ + 1.0f), (float)(border.top_ + border.bottom_));
+            float sliderPos = (float)(GetHeight() - sliderLength) * value_ / range_;
+            knob_->SetSize(GetWidth(), sliderLength);
+            knob_->SetPosition(0, Clamp((int)(sliderPos + 0.5f), 0, GetHeight() - knob_->GetHeight()));
+        }
+    }
+    else
+    {
+        knob_->SetSize(GetSize());
+        knob_->SetPosition(0, 0);
+    }
+}
+
+void Slider::Page(const IntVector2& position, bool pressed)
+{
+    if (!editable_)
+        return;
+
+    IntVector2 offsetXY = position - knob_->GetPosition() - knob_->GetSize() / 2;
+    int offset = orientation_ == O_HORIZONTAL ? offsetXY.x_ : offsetXY.y_;
+    float length = (float)(orientation_ == O_HORIZONTAL ? GetWidth() : GetHeight());
+
+    using namespace SliderPaged;
+
+    VariantMap& eventData = GetEventDataMap();
+    eventData[P_ELEMENT] = this;
+    eventData[P_OFFSET] = offset;
+
+    // Start transmitting repeated pages after the initial press
+    if (selected_ && pressed && repeatRate_ > 0.0f &&
+        repeatTimer_.GetMSec(false) >= Lerp(1000.0f / repeatRate_, 0, Abs(offset) / length))
+        repeatTimer_.Reset();
+    else
+        pressed = false;
+
+    eventData[P_PRESSED] = pressed;
+
+    SendEvent(E_SLIDERPAGED, eventData);
+}
+
+}
+
+}

+ 126 - 0
Source/Atomic/UI/SystemUI/Slider.h

@@ -0,0 +1,126 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "BorderImage.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+/// %Slider bar %UI element.
+class ATOMIC_API Slider : public BorderImage
+{
+    OBJECT(Slider);
+
+public:
+    /// Construct.
+    Slider(Context* context);
+    /// Destruct.
+    virtual ~Slider();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Perform UI element update.
+    virtual void Update(float timeStep);
+    /// React to mouse hover.
+    virtual void OnHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
+    /// React to mouse click begin.
+    virtual void OnClickBegin
+        (const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers, Cursor* cursor);
+    /// React to mouse click end.
+    virtual void OnClickEnd
+        (const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers, Cursor* cursor,
+            UIElement* beginElement);
+    /// React to mouse drag begin.
+    virtual void
+        OnDragBegin(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
+    /// React to mouse drag motion.
+    virtual void OnDragMove
+        (const IntVector2& position, const IntVector2& screenPosition, const IntVector2& deltaPos, int buttons, int qualifiers,
+            Cursor* cursor);
+    /// React to mouse drag end.
+    virtual void
+        OnDragEnd(const IntVector2& position, const IntVector2& screenPosition, int dragButtons, int buttons, Cursor* cursor);
+    /// React to resize.
+    virtual void OnResize();
+
+    /// Set orientation type.
+    void SetOrientation(Orientation orientation);
+    /// Set slider range maximum value (minimum value is always 0.)
+    void SetRange(float range);
+    /// Set slider current value.
+    void SetValue(float value);
+    /// Change value by a delta.
+    void ChangeValue(float delta);
+    /// Set paging minimum repeat rate (number of events per second).
+    void SetRepeatRate(float rate);
+
+    /// Return orientation type.
+    Orientation GetOrientation() const { return orientation_; }
+
+    /// Return slider range.
+    float GetRange() const { return range_; }
+
+    /// Return slider current value.
+    float GetValue() const { return value_; }
+
+    /// Return knob element.
+    BorderImage* GetKnob() const { return knob_; }
+
+    /// Return paging minimum repeat rate (number of events per second).
+    float GetRepeatRate() const { return repeatRate_; }
+
+protected:
+    /// Filter implicit attributes in serialization process.
+    virtual bool FilterImplicitAttributes(XMLElement& dest) const;
+    /// Update slider knob position & size.
+    void UpdateSlider();
+    /// Send slider page event.
+    void Page(const IntVector2& position, bool pressed);
+
+    /// Slider knob.
+    SharedPtr<BorderImage> knob_;
+    /// Orientation.
+    Orientation orientation_;
+    /// Slider range.
+    float range_;
+    /// Slider current value.
+    float value_;
+    /// Internal flag of whether the slider is being dragged.
+    bool dragSlider_;
+    /// Original mouse cursor position at drag begin.
+    IntVector2 dragBeginCursor_;
+    /// Original slider position at drag begin.
+    IntVector2 dragBeginPosition_;
+    /// Paging repeat rate.
+    float repeatRate_;
+    /// Paging minimum repeat timer.
+    Timer repeatTimer_;
+};
+
+}
+
+}

+ 287 - 0
Source/Atomic/UI/SystemUI/Sprite.cpp

@@ -0,0 +1,287 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Graphics/Texture2D.h"
+#include "../../Resource/ResourceCache.h"
+#include "Sprite.h"
+
+#include "../../DebugNew.h"
+
+namespace Atomic
+{
+
+extern const char* blendModeNames[];
+
+namespace SystemUI
+{
+
+extern const char* horizontalAlignments[];
+extern const char* verticalAlignments[];
+extern const char* UI_CATEGORY;
+
+Sprite::Sprite(Context* context) :
+    UIElement(context),
+    floatPosition_(Vector2::ZERO),
+    hotSpot_(IntVector2::ZERO),
+    scale_(Vector2::ONE),
+    rotation_(0.0f),
+    imageRect_(IntRect::ZERO),
+    blendMode_(BLEND_REPLACE)
+{
+}
+
+Sprite::~Sprite()
+{
+}
+
+void Sprite::RegisterObject(Context* context)
+{
+    context->RegisterFactory<Sprite>(UI_CATEGORY);
+
+    ACCESSOR_ATTRIBUTE("Name", GetName, SetName, String, String::EMPTY, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Position", GetPosition, SetPosition, Vector2, Vector2::ZERO, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Size", GetSize, SetSize, IntVector2, IntVector2::ZERO, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Hotspot", GetHotSpot, SetHotSpot, IntVector2, IntVector2::ZERO, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Scale", GetScale, SetScale, Vector2, Vector2::ONE, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Rotation", GetRotation, SetRotation, float, 0.0f, AM_FILE);
+    MIXED_ACCESSOR_ATTRIBUTE("Texture", GetTextureAttr, SetTextureAttr, ResourceRef, ResourceRef(Texture2D::GetTypeStatic()),
+        AM_FILE);
+    ACCESSOR_ATTRIBUTE("Image Rect", GetImageRect, SetImageRect, IntRect, IntRect::ZERO, AM_FILE);
+    ENUM_ACCESSOR_ATTRIBUTE("Blend Mode", GetBlendMode, SetBlendMode, BlendMode, blendModeNames, 0, AM_FILE);
+    ENUM_ACCESSOR_ATTRIBUTE("Horiz Alignment", GetHorizontalAlignment, SetHorizontalAlignment, HorizontalAlignment,
+        horizontalAlignments, HA_LEFT, AM_FILE);
+    ENUM_ACCESSOR_ATTRIBUTE("Vert Alignment", GetVerticalAlignment, SetVerticalAlignment, VerticalAlignment, verticalAlignments,
+        VA_TOP, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Priority", GetPriority, SetPriority, int, 0, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Opacity", GetOpacity, SetOpacity, float, 1.0f, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Color", GetColorAttr, SetColor, Color, Color::WHITE, AM_FILE);
+    ATTRIBUTE("Top Left Color", Color, color_[0], Color::WHITE, AM_FILE);
+    ATTRIBUTE("Top Right Color", Color, color_[1], Color::WHITE, AM_FILE);
+    ATTRIBUTE("Bottom Left Color", Color, color_[2], Color::WHITE, AM_FILE);
+    ATTRIBUTE("Bottom Right Color", Color, color_[3], Color::WHITE, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Is Visible", IsVisible, SetVisible, bool, true, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Use Derived Opacity", GetUseDerivedOpacity, SetUseDerivedOpacity, bool, true, AM_FILE);
+    ATTRIBUTE("Variables", VariantMap, vars_, Variant::emptyVariantMap, AM_FILE);
+}
+
+bool Sprite::IsWithinScissor(const IntRect& currentScissor)
+{
+    /// \todo Implement properly, for now just checks visibility flag
+    return visible_;
+}
+
+const IntVector2& Sprite::GetScreenPosition() const
+{
+    // This updates screen position for a sprite
+    GetTransform();
+    return screenPosition_;
+}
+
+void Sprite::GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor)
+{
+    bool allOpaque = true;
+    if (GetDerivedOpacity() < 1.0f || color_[C_TOPLEFT].a_ < 1.0f || color_[C_TOPRIGHT].a_ < 1.0f ||
+        color_[C_BOTTOMLEFT].a_ < 1.0f || color_[C_BOTTOMRIGHT].a_ < 1.0f)
+        allOpaque = false;
+
+    const IntVector2& size = GetSize();
+    SystemUIBatch
+        batch(this, blendMode_ == BLEND_REPLACE && !allOpaque ? BLEND_ALPHA : blendMode_, currentScissor, texture_, &vertexData);
+
+    batch.AddQuad(GetTransform(), 0, 0, size.x_, size.y_, imageRect_.left_, imageRect_.top_, imageRect_.right_ - imageRect_.left_,
+        imageRect_.bottom_ - imageRect_.top_);
+
+    SystemUIBatch::AddOrMerge(batch, batches);
+
+    // Reset hovering for next frame
+    hovering_ = false;
+}
+
+void Sprite::OnPositionSet()
+{
+    // If the integer position was set (layout update?), copy to the float position
+    floatPosition_ = Vector2((float)position_.x_, (float)position_.y_);
+}
+
+void Sprite::SetPosition(const Vector2& position)
+{
+    if (position != floatPosition_)
+    {
+        floatPosition_ = position;
+        // Copy to the integer position
+        position_ = IntVector2((int)position.x_, (int)position.y_);
+        MarkDirty();
+    }
+}
+
+void Sprite::SetPosition(float x, float y)
+{
+    SetPosition(Vector2(x, y));
+}
+
+void Sprite::SetHotSpot(const IntVector2& hotSpot)
+{
+    if (hotSpot != hotSpot_)
+    {
+        hotSpot_ = hotSpot;
+        MarkDirty();
+    }
+}
+
+void Sprite::SetHotSpot(int x, int y)
+{
+    SetHotSpot(IntVector2(x, y));
+}
+
+void Sprite::SetScale(const Vector2& scale)
+{
+    if (scale != scale_)
+    {
+        scale_ = scale;
+        MarkDirty();
+    }
+}
+
+void Sprite::SetScale(float x, float y)
+{
+    SetScale(Vector2(x, y));
+}
+
+void Sprite::SetScale(float scale)
+{
+    SetScale(Vector2(scale, scale));
+}
+
+void Sprite::SetRotation(float angle)
+{
+    if (angle != rotation_)
+    {
+        rotation_ = angle;
+        MarkDirty();
+    }
+}
+
+void Sprite::SetTexture(Texture* texture)
+{
+    texture_ = texture;
+    if (imageRect_ == IntRect::ZERO)
+        SetFullImageRect();
+}
+
+void Sprite::SetImageRect(const IntRect& rect)
+{
+    if (rect != IntRect::ZERO)
+        imageRect_ = rect;
+}
+
+void Sprite::SetFullImageRect()
+{
+    if (texture_)
+        SetImageRect(IntRect(0, 0, texture_->GetWidth(), texture_->GetHeight()));
+}
+
+void Sprite::SetBlendMode(BlendMode mode)
+{
+    blendMode_ = mode;
+}
+
+const Matrix3x4& Sprite::GetTransform() const
+{
+    if (positionDirty_)
+    {
+        Vector2 pos = floatPosition_;
+
+        Matrix3x4 parentTransform;
+
+        if (parent_)
+        {
+            Sprite* parentSprite = dynamic_cast<Sprite*>(parent_);
+            if (parentSprite)
+                parentTransform = parentSprite->GetTransform();
+            else
+            {
+                const IntVector2& parentScreenPos = parent_->GetScreenPosition() + parent_->GetChildOffset();
+                parentTransform = Matrix3x4::IDENTITY;
+                parentTransform.SetTranslation(Vector3((float)parentScreenPos.x_, (float)parentScreenPos.y_, 0.0f));
+            }
+
+            switch (GetHorizontalAlignment())
+            {
+            case HA_LEFT:
+                break;
+
+            case HA_CENTER:
+                pos.x_ += (float)(parent_->GetSize().x_ / 2);
+                break;
+
+            case HA_RIGHT:
+                pos.x_ += (float)parent_->GetSize().x_;
+                break;
+            }
+            switch (GetVerticalAlignment())
+            {
+            case VA_TOP:
+                break;
+
+            case VA_CENTER:
+                pos.y_ += (float)(parent_->GetSize().y_ / 2);
+                break;
+
+            case VA_BOTTOM:
+                pos.y_ += (float)(parent_->GetSize().y_);
+                break;
+            }
+        }
+        else
+            parentTransform = Matrix3x4::IDENTITY;
+
+        Matrix3x4 hotspotAdjust(Matrix3x4::IDENTITY);
+        hotspotAdjust.SetTranslation(Vector3((float)-hotSpot_.x_, (float)-hotSpot_.y_, 0.0f));
+
+        Matrix3x4 mainTransform(Vector3(pos, 0.0f), Quaternion(rotation_, Vector3::FORWARD), Vector3(scale_, 1.0f));
+
+        transform_ = parentTransform * mainTransform * hotspotAdjust;
+        positionDirty_ = false;
+
+        // Calculate an approximate screen position for GetElementAt(), or pixel-perfect child elements
+        Vector3 topLeftCorner = transform_ * Vector3::ZERO;
+        screenPosition_ = IntVector2((int)topLeftCorner.x_, (int)topLeftCorner.y_);
+    }
+
+    return transform_;
+}
+
+void Sprite::SetTextureAttr(const ResourceRef& value)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    SetTexture(cache->GetResource<Texture2D>(value.name_));
+}
+
+ResourceRef Sprite::GetTextureAttr() const
+{
+    return GetResourceRef(texture_, Texture2D::GetTypeStatic());
+}
+
+}
+
+}

+ 130 - 0
Source/Atomic/UI/SystemUI/Sprite.h

@@ -0,0 +1,130 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "../../Math/Matrix3x4.h"
+#include "UIElement.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+/// %UI element which allows sub-pixel positioning and size, as well as rotation. Only other Sprites should be added as child elements.
+class ATOMIC_API Sprite : public UIElement
+{
+    OBJECT(Sprite);
+
+public:
+    /// Construct.
+    Sprite(Context* context);
+    /// Destruct.
+    virtual ~Sprite();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Return whether is visible and inside a scissor rectangle and should be rendered.
+    virtual bool IsWithinScissor(const IntRect& currentScissor);
+    /// Update and return screen position.
+    virtual const IntVector2& GetScreenPosition() const;
+    /// Return UI rendering batches.
+    virtual void GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor);
+    /// React to position change.
+    virtual void OnPositionSet();
+
+    /// Set floating point position.
+    void SetPosition(const Vector2& position);
+    /// Set floating point position.
+    void SetPosition(float x, float y);
+    /// Set hotspot for positioning and rotation.
+    void SetHotSpot(const IntVector2& hotSpot);
+    /// Set hotspot for positioning and rotation.
+    void SetHotSpot(int x, int y);
+    /// Set scale. Scale also affects child sprites.
+    void SetScale(const Vector2& scale);
+    /// Set scale. Scale also affects child sprites.
+    void SetScale(float x, float y);
+    /// Set uniform scale. Scale also affects child sprites.
+    void SetScale(float scale);
+    /// Set rotation angle.
+    void SetRotation(float angle);
+    /// Set texture.
+    void SetTexture(Texture* texture);
+    /// Set part of texture to use as the image.
+    void SetImageRect(const IntRect& rect);
+    /// Use whole texture as the image.
+    void SetFullImageRect();
+    /// Set blend mode.
+    void SetBlendMode(BlendMode mode);
+
+    /// Return floating point position.
+    const Vector2& GetPosition() const { return floatPosition_; }
+
+    /// Return hotspot.
+    const IntVector2& GetHotSpot() const { return hotSpot_; }
+
+    /// Return scale.
+    const Vector2& GetScale() const { return scale_; }
+
+    /// Return rotation angle.
+    float GetRotation() const { return rotation_; }
+
+    /// Return texture.
+    Texture* GetTexture() const { return texture_; }
+
+    /// Return image rectangle.
+    const IntRect& GetImageRect() const { return imageRect_; }
+
+    /// Return blend mode.
+    BlendMode GetBlendMode() const { return blendMode_; }
+
+    /// Set texture attribute.
+    void SetTextureAttr(const ResourceRef& value);
+    /// Return texture attribute.
+    ResourceRef GetTextureAttr() const;
+    /// Update and return rendering transform, also used to transform child sprites.
+    const Matrix3x4& GetTransform() const;
+
+protected:
+    /// Floating point position.
+    Vector2 floatPosition_;
+    /// Hotspot for positioning and rotation.
+    IntVector2 hotSpot_;
+    /// Scale.
+    Vector2 scale_;
+    /// Rotation angle.
+    float rotation_;
+    /// Texture.
+    SharedPtr<Texture> texture_;
+    /// Image rectangle.
+    IntRect imageRect_;
+    /// Blend mode flag.
+    BlendMode blendMode_;
+    /// Transform matrix.
+    mutable Matrix3x4 transform_;
+};
+
+}
+
+}

+ 1815 - 0
Source/Atomic/UI/SystemUI/SystemUI.cpp

@@ -0,0 +1,1815 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Core/CoreEvents.h"
+#include "../../Core/Profiler.h"
+#include "../../Container/Sort.h"
+#include "../../Graphics/Graphics.h"
+#include "../../Graphics/GraphicsEvents.h"
+#include "../../Graphics/Shader.h"
+#include "../../Graphics/ShaderVariation.h"
+#include "../../Graphics/Texture2D.h"
+#include "../../Graphics/VertexBuffer.h"
+#include "../../Input/Input.h"
+#include "../../Input/InputEvents.h"
+#include "../../IO/Log.h"
+#include "../../Math/Matrix3x4.h"
+#include "../../Resource/ResourceCache.h"
+#include "CheckBox.h"
+#include "Cursor.h"
+#include "DropDownList.h"
+#include "Font.h"
+#include "LineEdit.h"
+#include "ListView.h"
+#include "MessageBox.h"
+#include "ScrollBar.h"
+#include "Slider.h"
+#include "Sprite.h"
+#include "Text.h"
+#include "ToolTip.h"
+#include "SystemUI.h"
+#include "SystemUIEvents.h"
+#include "Window.h"
+
+#include "Console.h"
+#include "DebugHud.h"
+
+#include <SDL/include/SDL.h>
+
+#include "../../DebugNew.h"
+
+#define TOUCHID_MASK(id) (1 << id)
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+StringHash VAR_ORIGIN("Origin");
+const StringHash VAR_ORIGINAL_PARENT("OriginalParent");
+const StringHash VAR_ORIGINAL_CHILD_INDEX("OriginalChildIndex");
+const StringHash VAR_PARENT_CHANGED("ParentChanged");
+
+const float DEFAULT_DOUBLECLICK_INTERVAL = 0.5f;
+const float DEFAULT_DRAGBEGIN_INTERVAL = 0.5f;
+const float DEFAULT_TOOLTIP_DELAY = 0.5f;
+const int DEFAULT_DRAGBEGIN_DISTANCE = 5;
+const int DEFAULT_FONT_TEXTURE_MAX_SIZE = 2048;
+
+const char* UI_CATEGORY = "UI";
+
+SystemUI::SystemUI(Context* context) :
+    Object(context),
+    rootElement_(new UIElement(context)),
+    rootModalElement_(new UIElement(context)),
+    doubleClickInterval_(DEFAULT_DOUBLECLICK_INTERVAL),
+    dragBeginInterval_(DEFAULT_DRAGBEGIN_INTERVAL),
+    defaultToolTipDelay_(DEFAULT_TOOLTIP_DELAY),
+    dragBeginDistance_(DEFAULT_DRAGBEGIN_DISTANCE),
+    mouseButtons_(0),
+    lastMouseButtons_(0),
+    qualifiers_(0),
+    maxFontTextureSize_(DEFAULT_FONT_TEXTURE_MAX_SIZE),
+    initialized_(false),
+    usingTouchInput_(false),
+#ifdef WIN32
+    nonFocusedMouseWheel_(false),    // Default MS Windows behaviour
+#else
+    nonFocusedMouseWheel_(true),     // Default Mac OS X and Linux behaviour
+#endif
+    useSystemClipboard_(false),
+#if defined(ANDROID) || defined(IOS)
+    useScreenKeyboard_(true),
+#else
+    useScreenKeyboard_(false),
+#endif
+    useMutableGlyphs_(false),
+    forceAutoHint_(false),
+    uiRendered_(false),
+    nonModalBatchSize_(0),
+    dragElementsCount_(0),
+    dragConfirmedCount_(0)
+{
+    rootElement_->SetTraversalMode(TM_DEPTH_FIRST);
+    rootModalElement_->SetTraversalMode(TM_DEPTH_FIRST);
+
+    // Register UI library object factories
+    RegisterSystemUILibrary(context_);
+
+    SubscribeToEvent(E_SCREENMODE, HANDLER(SystemUI, HandleScreenMode));
+    SubscribeToEvent(E_MOUSEBUTTONDOWN, HANDLER(SystemUI, HandleMouseButtonDown));
+    SubscribeToEvent(E_MOUSEBUTTONUP, HANDLER(SystemUI, HandleMouseButtonUp));
+    SubscribeToEvent(E_MOUSEMOVE, HANDLER(SystemUI, HandleMouseMove));
+    SubscribeToEvent(E_MOUSEWHEEL, HANDLER(SystemUI, HandleMouseWheel));
+    SubscribeToEvent(E_TOUCHBEGIN, HANDLER(SystemUI, HandleTouchBegin));
+    SubscribeToEvent(E_TOUCHEND, HANDLER(SystemUI, HandleTouchEnd));
+    SubscribeToEvent(E_TOUCHMOVE, HANDLER(SystemUI, HandleTouchMove));
+    SubscribeToEvent(E_KEYDOWN, HANDLER(SystemUI, HandleKeyDown));
+    SubscribeToEvent(E_TEXTINPUT, HANDLER(SystemUI, HandleTextInput));
+    SubscribeToEvent(E_DROPFILE, HANDLER(SystemUI, HandleDropFile));
+
+    // Try to initialize right now, but skip if screen mode is not yet set
+    Initialize();
+}
+
+SystemUI::~SystemUI()
+{
+}
+
+void SystemUI::SetCursor(Cursor* cursor)
+{
+    // Remove old cursor (if any) and set new
+    if (cursor_)
+    {
+        rootElement_->RemoveChild(cursor_);
+        cursor_.Reset();
+    }
+    if (cursor)
+    {
+        rootElement_->AddChild(cursor);
+        cursor_ = cursor;
+
+        IntVector2 pos = cursor_->GetPosition();
+        const IntVector2& rootSize = rootElement_->GetSize();
+        pos.x_ = Clamp(pos.x_, 0, rootSize.x_ - 1);
+        pos.y_ = Clamp(pos.y_, 0, rootSize.y_ - 1);
+        cursor_->SetPosition(pos);
+    }
+}
+
+void SystemUI::SetFocusElement(UIElement* element, bool byKey)
+{
+    using namespace FocusChanged;
+
+    UIElement* originalElement = element;
+
+    if (element)
+    {
+        // Return if already has focus
+        if (focusElement_ == element)
+            return;
+
+        // Only allow child elements of the modal element to receive focus
+        if (HasModalElement())
+        {
+            UIElement* topLevel = element->GetParent();
+            while (topLevel && topLevel->GetParent() != rootElement_)
+                topLevel = topLevel->GetParent();
+            if (topLevel)   // If parented to non-modal root then ignore
+                return;
+        }
+
+        // Search for an element in the hierarchy that can alter focus. If none found, exit
+        element = GetFocusableElement(element);
+        if (!element)
+            return;
+    }
+
+    // Remove focus from the old element
+    if (focusElement_)
+    {
+        UIElement* oldFocusElement = focusElement_;
+        focusElement_.Reset();
+
+        VariantMap& focusEventData = GetEventDataMap();
+        focusEventData[Defocused::P_ELEMENT] = oldFocusElement;
+        oldFocusElement->SendEvent(E_DEFOCUSED, focusEventData);
+    }
+
+    // Then set focus to the new
+    if (element && element->GetFocusMode() >= FM_FOCUSABLE)
+    {
+        focusElement_ = element;
+
+        VariantMap& focusEventData = GetEventDataMap();
+        focusEventData[Focused::P_ELEMENT] = element;
+        focusEventData[Focused::P_BYKEY] = byKey;
+        element->SendEvent(E_FOCUSED, focusEventData);
+    }
+
+    VariantMap& eventData = GetEventDataMap();
+    eventData[P_CLICKEDELEMENT] = originalElement;
+    eventData[P_ELEMENT] = element;
+    SendEvent(E_FOCUSCHANGED, eventData);
+}
+
+bool SystemUI::SetModalElement(UIElement* modalElement, bool enable)
+{
+    if (!modalElement)
+        return false;
+
+    // Currently only allow modal window
+    if (modalElement->GetType() != Window::GetTypeStatic())
+        return false;
+
+    assert(rootModalElement_);
+    UIElement* currParent = modalElement->GetParent();
+    if (enable)
+    {
+        // Make sure it is not already the child of the root modal element
+        if (currParent == rootModalElement_)
+            return false;
+
+        // Adopt modal root as parent
+        modalElement->SetVar(VAR_ORIGINAL_PARENT, currParent);
+        modalElement->SetVar(VAR_ORIGINAL_CHILD_INDEX, currParent ? currParent->FindChild(modalElement) : M_MAX_UNSIGNED);
+        modalElement->SetParent(rootModalElement_);
+
+        // If it is a popup element, bring along its top-level parent
+        UIElement* originElement = static_cast<UIElement*>(modalElement->GetVar(VAR_ORIGIN).GetPtr());
+        if (originElement)
+        {
+            UIElement* element = originElement;
+            while (element && element->GetParent() != rootElement_)
+                element = element->GetParent();
+            if (element)
+            {
+                originElement->SetVar(VAR_PARENT_CHANGED, element);
+                UIElement* oriParent = element->GetParent();
+                element->SetVar(VAR_ORIGINAL_PARENT, oriParent);
+                element->SetVar(VAR_ORIGINAL_CHILD_INDEX, oriParent ? oriParent->FindChild(element) : M_MAX_UNSIGNED);
+                element->SetParent(rootModalElement_);
+            }
+        }
+
+        return true;
+    }
+    else
+    {
+        // Only the modal element can disable itself
+        if (currParent != rootModalElement_)
+            return false;
+
+        // Revert back to original parent
+        modalElement->SetParent(static_cast<UIElement*>(modalElement->GetVar(VAR_ORIGINAL_PARENT).GetPtr()),
+            modalElement->GetVar(VAR_ORIGINAL_CHILD_INDEX).GetUInt());
+        VariantMap& vars = const_cast<VariantMap&>(modalElement->GetVars());
+        vars.Erase(VAR_ORIGINAL_PARENT);
+        vars.Erase(VAR_ORIGINAL_CHILD_INDEX);
+
+        // If it is a popup element, revert back its top-level parent
+        UIElement* originElement = static_cast<UIElement*>(modalElement->GetVar(VAR_ORIGIN).GetPtr());
+        if (originElement)
+        {
+            UIElement* element = static_cast<UIElement*>(originElement->GetVar(VAR_PARENT_CHANGED).GetPtr());
+            if (element)
+            {
+                const_cast<VariantMap&>(originElement->GetVars()).Erase(VAR_PARENT_CHANGED);
+                element->SetParent(static_cast<UIElement*>(element->GetVar(VAR_ORIGINAL_PARENT).GetPtr()),
+                    element->GetVar(VAR_ORIGINAL_CHILD_INDEX).GetUInt());
+                vars = const_cast<VariantMap&>(element->GetVars());
+                vars.Erase(VAR_ORIGINAL_PARENT);
+                vars.Erase(VAR_ORIGINAL_CHILD_INDEX);
+            }
+        }
+
+        return true;
+    }
+}
+
+void SystemUI::Clear()
+{
+    rootElement_->RemoveAllChildren();
+    rootModalElement_->RemoveAllChildren();
+    if (cursor_)
+        rootElement_->AddChild(cursor_);
+}
+
+void SystemUI::Update(float timeStep)
+{
+    assert(rootElement_ && rootModalElement_);
+
+    PROFILE(UpdateUI);
+
+    // Expire hovers
+    for (HashMap<WeakPtr<UIElement>, bool>::Iterator i = hoveredElements_.Begin(); i != hoveredElements_.End(); ++i)
+        i->second_ = false;
+
+    Input* input = GetSubsystem<Input>();
+    bool mouseGrabbed = input->IsMouseGrabbed();
+
+    IntVector2 cursorPos;
+    bool cursorVisible;
+    GetCursorPositionAndVisible(cursorPos, cursorVisible);
+
+    // Drag begin based on time
+    if (dragElementsCount_ > 0 && !mouseGrabbed)
+    {
+        for (HashMap<WeakPtr<UIElement>, SystemUI::DragData*>::Iterator i = dragElements_.Begin(); i != dragElements_.End();)
+        {
+            WeakPtr<UIElement> dragElement = i->first_;
+            SystemUI::DragData* dragData = i->second_;
+
+            if (!dragElement)
+            {
+                i = DragElementErase(i);
+                continue;
+            }
+
+            if (!dragData->dragBeginPending)
+            {
+                ++i;
+                continue;
+            }
+
+            if (dragData->dragBeginTimer.GetMSec(false) >= (unsigned)(dragBeginInterval_ * 1000))
+            {
+                dragData->dragBeginPending = false;
+                IntVector2 beginSendPos = dragData->dragBeginSumPos / dragData->numDragButtons;
+                dragConfirmedCount_++;
+                if (!usingTouchInput_)
+                    dragElement->OnDragBegin(dragElement->ScreenToElement(beginSendPos), beginSendPos, dragData->dragButtons,
+                        qualifiers_, cursor_);
+                else
+                    dragElement->OnDragBegin(dragElement->ScreenToElement(beginSendPos), beginSendPos, dragData->dragButtons, 0, 0);
+
+                SendDragOrHoverEvent(E_DRAGBEGIN, dragElement, beginSendPos, IntVector2::ZERO, dragData);
+            }
+
+            ++i;
+        }
+    }
+
+    // Mouse hover
+    if (!mouseGrabbed && !input->GetTouchEmulation())
+    {
+        if (!usingTouchInput_ && cursorVisible)
+            ProcessHover(cursorPos, mouseButtons_, qualifiers_, cursor_);
+    }
+
+    // Touch hover
+    unsigned numTouches = input->GetNumTouches();
+    for (unsigned i = 0; i < numTouches; ++i)
+    {
+        TouchState* touch = input->GetTouch(i);
+        ProcessHover(touch->position_, TOUCHID_MASK(touch->touchID_), 0, 0);
+    }
+
+    // End hovers that expired without refreshing
+    for (HashMap<WeakPtr<UIElement>, bool>::Iterator i = hoveredElements_.Begin(); i != hoveredElements_.End();)
+    {
+        if (i->first_.Expired() || !i->second_)
+        {
+            UIElement* element = i->first_;
+            if (element)
+            {
+                using namespace HoverEnd;
+
+                VariantMap& eventData = GetEventDataMap();
+                eventData[P_ELEMENT] = element;
+                element->SendEvent(E_HOVEREND, eventData);
+            }
+            i = hoveredElements_.Erase(i);
+        }
+        else
+            ++i;
+    }
+
+    Update(timeStep, rootElement_);
+    Update(timeStep, rootModalElement_);
+}
+
+void SystemUI::RenderUpdate()
+{
+    assert(rootElement_ && rootModalElement_ && graphics_);
+
+    PROFILE(GetUIBatches);
+
+    uiRendered_ = false;
+
+    // If the OS cursor is visible, do not render the UI's own cursor
+    bool osCursorVisible = GetSubsystem<Input>()->IsMouseVisible();
+
+    // Get rendering batches from the non-modal UI elements
+    batches_.Clear();
+    vertexData_.Clear();
+    const IntVector2& rootSize = rootElement_->GetSize();
+    IntRect currentScissor = IntRect(0, 0, rootSize.x_, rootSize.y_);
+    if (rootElement_->IsVisible())
+        GetBatches(rootElement_, currentScissor);
+
+    // Save the batch size of the non-modal batches for later use
+    nonModalBatchSize_ = batches_.Size();
+
+    // Get rendering batches from the modal UI elements
+    GetBatches(rootModalElement_, currentScissor);
+
+    // Get batches from the cursor (and its possible children) last to draw it on top of everything
+    if (cursor_ && cursor_->IsVisible() && !osCursorVisible)
+    {
+        currentScissor = IntRect(0, 0, rootSize.x_, rootSize.y_);
+        cursor_->GetBatches(batches_, vertexData_, currentScissor);
+        GetBatches(cursor_, currentScissor);
+    }
+}
+
+void SystemUI::Render(bool resetRenderTargets)
+{
+    // Perform the default render only if not rendered yet
+    if (resetRenderTargets && uiRendered_)
+        return;
+
+    PROFILE(RenderUI);
+
+    // If the OS cursor is visible, apply its shape now if changed
+    bool osCursorVisible = GetSubsystem<Input>()->IsMouseVisible();
+    if (cursor_ && osCursorVisible)
+        cursor_->ApplyOSCursorShape();
+
+    SetVertexData(vertexBuffer_, vertexData_);
+    SetVertexData(debugVertexBuffer_, debugVertexData_);
+
+    // Render non-modal batches
+    Render(resetRenderTargets, vertexBuffer_, batches_, 0, nonModalBatchSize_);
+    // Render debug draw
+    Render(resetRenderTargets, debugVertexBuffer_, debugDrawBatches_, 0, debugDrawBatches_.Size());
+    // Render modal batches
+    Render(resetRenderTargets, vertexBuffer_, batches_, nonModalBatchSize_, batches_.Size());
+
+    // Clear the debug draw batches and data
+    debugDrawBatches_.Clear();
+    debugVertexData_.Clear();
+
+    uiRendered_ = true;
+}
+
+void SystemUI::DebugDraw(UIElement* element)
+{
+    if (element)
+    {
+        const IntVector2& rootSize = rootElement_->GetSize();
+        element->GetDebugDrawBatches(debugDrawBatches_, debugVertexData_, IntRect(0, 0, rootSize.x_, rootSize.y_));
+    }
+}
+
+SharedPtr<UIElement> SystemUI::LoadLayout(Deserializer& source, XMLFile* styleFile)
+{
+    SharedPtr<XMLFile> xml(new XMLFile(context_));
+    if (!xml->Load(source))
+        return SharedPtr<UIElement>();
+    else
+        return LoadLayout(xml, styleFile);
+}
+
+SharedPtr<UIElement> SystemUI::LoadLayout(XMLFile* file, XMLFile* styleFile)
+{
+    PROFILE(LoadUILayout);
+
+    SharedPtr<UIElement> root;
+
+    if (!file)
+    {
+        LOGERROR("Null UI layout XML file");
+        return root;
+    }
+
+    LOGDEBUG("Loading UI layout " + file->GetName());
+
+    XMLElement rootElem = file->GetRoot("element");
+    if (!rootElem)
+    {
+        LOGERROR("No root UI element in " + file->GetName());
+        return root;
+    }
+
+    String typeName = rootElem.GetAttribute("type");
+    if (typeName.Empty())
+        typeName = "UIElement";
+
+    root = DynamicCast<UIElement>(context_->CreateObject(typeName));
+    if (!root)
+    {
+        LOGERROR("Could not create unknown UI element " + typeName);
+        return root;
+    }
+
+    // Use default style file of the root element if it has one
+    if (!styleFile)
+        styleFile = rootElement_->GetDefaultStyle(false);
+    // Set it as default for later use by children elements
+    if (styleFile)
+        root->SetDefaultStyle(styleFile);
+
+    root->LoadXML(rootElem, styleFile);
+    return root;
+}
+
+bool SystemUI::SaveLayout(Serializer& dest, UIElement* element)
+{
+    PROFILE(SaveUILayout);
+
+    return element && element->SaveXML(dest);
+}
+
+void SystemUI::SetClipboardText(const String& text)
+{
+    clipBoard_ = text;
+    if (useSystemClipboard_)
+        SDL_SetClipboardText(text.CString());
+}
+
+void SystemUI::SetDoubleClickInterval(float interval)
+{
+    doubleClickInterval_ = Max(interval, 0.0f);
+}
+
+void SystemUI::SetDragBeginInterval(float interval)
+{
+    dragBeginInterval_ = Max(interval, 0.0f);
+}
+
+void SystemUI::SetDragBeginDistance(int pixels)
+{
+    dragBeginDistance_ = Max(pixels, 0);
+}
+
+void SystemUI::SetDefaultToolTipDelay(float delay)
+{
+    defaultToolTipDelay_ = Max(delay, 0.0f);
+}
+
+void SystemUI::SetMaxFontTextureSize(int size)
+{
+    if (IsPowerOfTwo((unsigned)size) && size >= FONT_TEXTURE_MIN_SIZE)
+    {
+        if (size != maxFontTextureSize_)
+        {
+            maxFontTextureSize_ = size;
+            ReleaseFontFaces();
+        }
+    }
+}
+
+void SystemUI::SetNonFocusedMouseWheel(bool nonFocusedMouseWheel)
+{
+    nonFocusedMouseWheel_ = nonFocusedMouseWheel;
+}
+
+void SystemUI::SetUseSystemClipboard(bool enable)
+{
+    useSystemClipboard_ = enable;
+}
+
+void SystemUI::SetUseScreenKeyboard(bool enable)
+{
+    useScreenKeyboard_ = enable;
+}
+
+void SystemUI::SetUseMutableGlyphs(bool enable)
+{
+    if (enable != useMutableGlyphs_)
+    {
+        useMutableGlyphs_ = enable;
+        ReleaseFontFaces();
+    }
+}
+
+void SystemUI::SetForceAutoHint(bool enable)
+{
+    if (enable != forceAutoHint_)
+    {
+        forceAutoHint_ = enable;
+        ReleaseFontFaces();
+    }
+}
+
+IntVector2 SystemUI::GetCursorPosition() const
+{
+    return cursor_ ? cursor_->GetPosition() : GetSubsystem<Input>()->GetMousePosition();
+}
+
+UIElement* SystemUI::GetElementAt(const IntVector2& position, bool enabledOnly)
+{
+    UIElement* result = 0;
+    GetElementAt(result, HasModalElement() ? rootModalElement_ : rootElement_, position, enabledOnly);
+    return result;
+}
+
+UIElement* SystemUI::GetElementAt(int x, int y, bool enabledOnly)
+{
+    return GetElementAt(IntVector2(x, y), enabledOnly);
+}
+
+UIElement* SystemUI::GetFrontElement() const
+{
+    const Vector<SharedPtr<UIElement> >& rootChildren = rootElement_->GetChildren();
+    int maxPriority = M_MIN_INT;
+    UIElement* front = 0;
+
+    for (unsigned i = 0; i < rootChildren.Size(); ++i)
+    {
+        // Do not take into account input-disabled elements, hidden elements or those that are always in the front
+        if (!rootChildren[i]->IsEnabled() || !rootChildren[i]->IsVisible() || !rootChildren[i]->GetBringToBack())
+            continue;
+
+        int priority = rootChildren[i]->GetPriority();
+        if (priority > maxPriority)
+        {
+            maxPriority = priority;
+            front = rootChildren[i];
+        }
+    }
+
+    return front;
+}
+
+const Vector<UIElement*> SystemUI::GetDragElements()
+{
+    // Do not return the element until drag begin event has actually been posted
+    if (!dragElementsConfirmed_.Empty())
+        return dragElementsConfirmed_;
+
+    for (HashMap<WeakPtr<UIElement>, SystemUI::DragData*>::Iterator i = dragElements_.Begin(); i != dragElements_.End();)
+    {
+        WeakPtr<UIElement> dragElement = i->first_;
+        SystemUI::DragData* dragData = i->second_;
+
+        if (!dragElement)
+        {
+            i = DragElementErase(i);
+            continue;
+        }
+
+        if (!dragData->dragBeginPending)
+            dragElementsConfirmed_.Push(dragElement);
+
+        ++i;
+    }
+
+    return dragElementsConfirmed_;
+}
+
+UIElement* SystemUI::GetDragElement(unsigned index)
+{
+    GetDragElements();
+    if (index >= dragElementsConfirmed_.Size())
+        return (UIElement*)0;
+
+    return dragElementsConfirmed_[index];
+}
+
+const String& SystemUI::GetClipboardText() const
+{
+    if (useSystemClipboard_)
+    {
+        char* text = SDL_GetClipboardText();
+        clipBoard_ = String(text);
+        if (text)
+            SDL_free(text);
+    }
+
+    return clipBoard_;
+}
+
+bool SystemUI::HasModalElement() const
+{
+    return rootModalElement_->GetNumChildren() > 0;
+}
+
+void SystemUI::Initialize()
+{
+    Graphics* graphics = GetSubsystem<Graphics>();
+
+    if (!graphics || !graphics->IsInitialized())
+        return;
+
+    PROFILE(InitUI);
+
+    graphics_ = graphics;
+    SystemUIBatch::posAdjust = Vector3(Graphics::GetPixelUVOffset(), 0.0f);
+
+    rootElement_->SetSize(graphics->GetWidth(), graphics->GetHeight());
+    rootModalElement_->SetSize(rootElement_->GetSize());
+
+    vertexBuffer_ = new VertexBuffer(context_);
+    debugVertexBuffer_ = new VertexBuffer(context_);
+
+    initialized_ = true;
+
+    SubscribeToEvent(E_BEGINFRAME, HANDLER(SystemUI, HandleBeginFrame));
+    SubscribeToEvent(E_POSTUPDATE, HANDLER(SystemUI, HandlePostUpdate));
+    SubscribeToEvent(E_RENDERUPDATE, HANDLER(SystemUI, HandleRenderUpdate));
+
+    LOGINFO("Initialized system user interface");
+}
+
+void SystemUI::Update(float timeStep, UIElement* element)
+{
+    // Keep a weak pointer to the element in case it destroys itself on update
+    WeakPtr<UIElement> elementWeak(element);
+
+    element->Update(timeStep);
+    if (elementWeak.Expired())
+        return;
+
+    const Vector<SharedPtr<UIElement> >& children = element->GetChildren();
+    // Update of an element may modify its child vector. Use just index-based iteration to be safe
+    for (unsigned i = 0; i < children.Size(); ++i)
+        Update(timeStep, children[i]);
+}
+
+void SystemUI::SetVertexData(VertexBuffer* dest, const PODVector<float>& vertexData)
+{
+    if (vertexData.Empty())
+        return;
+
+    // Update quad geometry into the vertex buffer
+    // Resize the vertex buffer first if too small or much too large
+    unsigned numVertices = vertexData.Size() / UI_VERTEX_SIZE;
+    if (dest->GetVertexCount() < numVertices || dest->GetVertexCount() > numVertices * 2)
+        dest->SetSize(numVertices, MASK_POSITION | MASK_COLOR | MASK_TEXCOORD1, true);
+
+    dest->SetData(&vertexData[0]);
+}
+
+void SystemUI::Render(bool resetRenderTargets, VertexBuffer* buffer, const PODVector<SystemUIBatch>& batches, unsigned batchStart,
+    unsigned batchEnd)
+{
+    // Engine does not render when window is closed or device is lost
+    assert(graphics_ && graphics_->IsInitialized() && !graphics_->IsDeviceLost());
+
+    if (batches.Empty())
+        return;
+
+    Vector2 invScreenSize(1.0f / (float)graphics_->GetWidth(), 1.0f / (float)graphics_->GetHeight());
+    Vector2 scale(2.0f * invScreenSize.x_, -2.0f * invScreenSize.y_);
+    Vector2 offset(-1.0f, 1.0f);
+
+    Matrix4 projection(Matrix4::IDENTITY);
+    projection.m00_ = scale.x_;
+    projection.m03_ = offset.x_;
+    projection.m11_ = scale.y_;
+    projection.m13_ = offset.y_;
+    projection.m22_ = 1.0f;
+    projection.m23_ = 0.0f;
+    projection.m33_ = 1.0f;
+
+    graphics_->ClearParameterSources();
+    graphics_->SetColorWrite(true);
+    graphics_->SetCullMode(CULL_CCW);
+    graphics_->SetDepthTest(CMP_ALWAYS);
+    graphics_->SetDepthWrite(false);
+    graphics_->SetFillMode(FILL_SOLID);
+    graphics_->SetStencilTest(false);
+    if (resetRenderTargets)
+        graphics_->ResetRenderTargets();
+    graphics_->SetVertexBuffer(buffer);
+
+    ShaderVariation* noTextureVS = graphics_->GetShader(VS, "Basic", "VERTEXCOLOR");
+    ShaderVariation* diffTextureVS = graphics_->GetShader(VS, "Basic", "DIFFMAP VERTEXCOLOR");
+    ShaderVariation* noTexturePS = graphics_->GetShader(PS, "Basic", "VERTEXCOLOR");
+    ShaderVariation* diffTexturePS = graphics_->GetShader(PS, "Basic", "DIFFMAP VERTEXCOLOR");
+    ShaderVariation* diffMaskTexturePS = graphics_->GetShader(PS, "Basic", "DIFFMAP ALPHAMASK VERTEXCOLOR");
+    ShaderVariation* alphaTexturePS = graphics_->GetShader(PS, "Basic", "ALPHAMAP VERTEXCOLOR");
+
+    unsigned alphaFormat = Graphics::GetAlphaFormat();
+
+    for (unsigned i = batchStart; i < batchEnd; ++i)
+    {
+        const SystemUIBatch& batch = batches[i];
+        if (batch.vertexStart_ == batch.vertexEnd_)
+            continue;
+
+        ShaderVariation* ps;
+        ShaderVariation* vs;
+
+        if (!batch.texture_)
+        {
+            ps = noTexturePS;
+            vs = noTextureVS;
+        }
+        else
+        {
+            // If texture contains only an alpha channel, use alpha shader (for fonts)
+            vs = diffTextureVS;
+
+            if (batch.texture_->GetFormat() == alphaFormat)
+                ps = alphaTexturePS;
+            else if (batch.blendMode_ != BLEND_ALPHA && batch.blendMode_ != BLEND_ADDALPHA && batch.blendMode_ != BLEND_PREMULALPHA)
+                ps = diffMaskTexturePS;
+            else
+                ps = diffTexturePS;
+        }
+
+        graphics_->SetShaders(vs, ps);
+        if (graphics_->NeedParameterUpdate(SP_OBJECT, this))
+            graphics_->SetShaderParameter(VSP_MODEL, Matrix3x4::IDENTITY);
+        if (graphics_->NeedParameterUpdate(SP_CAMERA, this))
+            graphics_->SetShaderParameter(VSP_VIEWPROJ, projection);
+        if (graphics_->NeedParameterUpdate(SP_MATERIAL, this))
+            graphics_->SetShaderParameter(PSP_MATDIFFCOLOR, Color(1.0f, 1.0f, 1.0f, 1.0f));
+
+        graphics_->SetBlendMode(batch.blendMode_);
+        graphics_->SetScissorTest(true, batch.scissor_);
+        graphics_->SetTexture(0, batch.texture_);
+        graphics_->Draw(TRIANGLE_LIST, batch.vertexStart_ / UI_VERTEX_SIZE,
+            (batch.vertexEnd_ - batch.vertexStart_) / UI_VERTEX_SIZE);
+    }
+}
+
+void SystemUI::GetBatches(UIElement* element, IntRect currentScissor)
+{
+    // Set clipping scissor for child elements. No need to draw if zero size
+    element->AdjustScissor(currentScissor);
+    if (currentScissor.left_ == currentScissor.right_ || currentScissor.top_ == currentScissor.bottom_)
+        return;
+
+    element->SortChildren();
+    const Vector<SharedPtr<UIElement> >& children = element->GetChildren();
+    if (children.Empty())
+        return;
+
+    // For non-root elements draw all children of same priority before recursing into their children: assumption is that they have
+    // same renderstate
+    Vector<SharedPtr<UIElement> >::ConstIterator i = children.Begin();
+    if (element->GetTraversalMode() == TM_BREADTH_FIRST)
+    {
+        Vector<SharedPtr<UIElement> >::ConstIterator j = i;
+        while (i != children.End())
+        {
+            int currentPriority = (*i)->GetPriority();
+            while (j != children.End() && (*j)->GetPriority() == currentPriority)
+            {
+                if ((*j)->IsWithinScissor(currentScissor) && (*j) != cursor_)
+                    (*j)->GetBatches(batches_, vertexData_, currentScissor);
+                ++j;
+            }
+            // Now recurse into the children
+            while (i != j)
+            {
+                if ((*i)->IsVisible() && (*i) != cursor_)
+                    GetBatches(*i, currentScissor);
+                ++i;
+            }
+        }
+    }
+    // On the root level draw each element and its children immediately after to avoid artifacts
+    else
+    {
+        while (i != children.End())
+        {
+            if ((*i) != cursor_)
+            {
+                if ((*i)->IsWithinScissor(currentScissor))
+                    (*i)->GetBatches(batches_, vertexData_, currentScissor);
+                if ((*i)->IsVisible())
+                    GetBatches(*i, currentScissor);
+            }
+            ++i;
+        }
+    }
+}
+
+void SystemUI::GetElementAt(UIElement*& result, UIElement* current, const IntVector2& position, bool enabledOnly)
+{
+    if (!current)
+        return;
+
+    current->SortChildren();
+    const Vector<SharedPtr<UIElement> >& children = current->GetChildren();
+    LayoutMode parentLayoutMode = current->GetLayoutMode();
+
+    for (unsigned i = 0; i < children.Size(); ++i)
+    {
+        UIElement* element = children[i];
+        bool hasChildren = element->GetNumChildren() > 0;
+
+        if (element != cursor_.Get() && element->IsVisible())
+        {
+            if (element->IsInside(position, true))
+            {
+                // Store the current result, then recurse into its children. Because children
+                // are sorted from lowest to highest priority, the topmost match should remain
+                if (element->IsEnabled() || !enabledOnly)
+                    result = element;
+
+                if (hasChildren)
+                    GetElementAt(result, element, position, enabledOnly);
+                // Layout optimization: if the element has no children, can break out after the first match
+                else if (parentLayoutMode != LM_FREE)
+                    break;
+            }
+            else
+            {
+                if (hasChildren)
+                {
+                    if (element->IsInsideCombined(position, true))
+                        GetElementAt(result, element, position, enabledOnly);
+                }
+                // Layout optimization: if position is much beyond the visible screen, check how many elements we can skip,
+                // or if we already passed all visible elements
+                else if (parentLayoutMode != LM_FREE)
+                {
+                    if (!i)
+                    {
+                        int screenPos = (parentLayoutMode == LM_HORIZONTAL) ? element->GetScreenPosition().x_ :
+                            element->GetScreenPosition().y_;
+                        int layoutMaxSize = current->GetLayoutMaxSize();
+
+                        if (screenPos < 0 && layoutMaxSize > 0)
+                        {
+                            unsigned toSkip = (unsigned)(-screenPos / layoutMaxSize);
+                            if (toSkip > 0)
+                                i += (toSkip - 1);
+                        }
+                    }
+                    else if (parentLayoutMode == LM_HORIZONTAL)
+                    {
+                        if (element->GetScreenPosition().x_ >= rootElement_->GetSize().x_)
+                            break;
+                    }
+                    else if (parentLayoutMode == LM_VERTICAL)
+                    {
+                        if (element->GetScreenPosition().y_ >= rootElement_->GetSize().y_)
+                            break;
+                    }
+                }
+            }
+        }
+    }
+}
+
+UIElement* SystemUI::GetFocusableElement(UIElement* element)
+{
+    while (element)
+    {
+        if (element->GetFocusMode() != FM_NOTFOCUSABLE)
+            break;
+        element = element->GetParent();
+    }
+    return element;
+}
+
+void SystemUI::GetCursorPositionAndVisible(IntVector2& pos, bool& visible)
+{
+    // Prefer software cursor then OS-specific cursor
+    if (cursor_ && cursor_->IsVisible())
+    {
+        pos = cursor_->GetPosition();
+        visible = true;
+    }
+    else if (GetSubsystem<Input>()->GetMouseMode() == MM_RELATIVE)
+        visible = true;
+    else
+    {
+        Input* input = GetSubsystem<Input>();
+        pos = input->GetMousePosition();
+        visible = input->IsMouseVisible();
+
+        if (!visible && cursor_)
+            pos = cursor_->GetPosition();
+    }
+}
+
+void SystemUI::SetCursorShape(CursorShape shape)
+{
+    if (cursor_)
+        cursor_->SetShape(shape);
+}
+
+void SystemUI::ReleaseFontFaces()
+{
+    LOGDEBUG("Reloading font faces");
+
+    PODVector<Font*> fonts;
+    GetSubsystem<ResourceCache>()->GetResources<Font>(fonts);
+
+    for (unsigned i = 0; i < fonts.Size(); ++i)
+        fonts[i]->ReleaseFaces();
+}
+
+void SystemUI::ProcessHover(const IntVector2& cursorPos, int buttons, int qualifiers, Cursor* cursor)
+{
+    WeakPtr<UIElement> element(GetElementAt(cursorPos));
+
+    for (HashMap<WeakPtr<UIElement>, SystemUI::DragData*>::Iterator i = dragElements_.Begin(); i != dragElements_.End();)
+    {
+        WeakPtr<UIElement> dragElement = i->first_;
+        SystemUI::DragData* dragData = i->second_;
+
+        if (!dragElement)
+        {
+            i = DragElementErase(i);
+            continue;
+        }
+
+        bool dragSource = dragElement && (dragElement->GetDragDropMode() & DD_SOURCE) != 0;
+        bool dragTarget = element && (element->GetDragDropMode() & DD_TARGET) != 0;
+        bool dragDropTest = dragSource && dragTarget && element != dragElement;
+        // If drag start event has not been posted yet, do not do drag handling here
+        if (dragData->dragBeginPending)
+            dragSource = dragTarget = dragDropTest = false;
+
+        // Hover effect
+        // If a drag is going on, transmit hover only to the element being dragged, unless it's a drop target
+        if (element && element->IsEnabled())
+        {
+            if (dragElement == element || dragDropTest)
+            {
+                element->OnHover(element->ScreenToElement(cursorPos), cursorPos, buttons, qualifiers, cursor);
+
+                // Begin hover event
+                if (!hoveredElements_.Contains(element))
+                {
+                    SendDragOrHoverEvent(E_HOVERBEGIN, element, cursorPos, IntVector2::ZERO, 0);
+                    // Exit if element is destroyed by the event handling
+                    if (!element)
+                        return;
+                }
+                hoveredElements_[element] = true;
+            }
+        }
+
+        // Drag and drop test
+        if (dragDropTest)
+        {
+            bool accept = element->OnDragDropTest(dragElement);
+            if (accept)
+            {
+                using namespace DragDropTest;
+
+                VariantMap& eventData = GetEventDataMap();
+                eventData[P_SOURCE] = dragElement.Get();
+                eventData[P_TARGET] = element.Get();
+                eventData[P_ACCEPT] = accept;
+                SendEvent(E_DRAGDROPTEST, eventData);
+                accept = eventData[P_ACCEPT].GetBool();
+            }
+
+            if (cursor)
+                cursor->SetShape(accept ? CS_ACCEPTDROP : CS_REJECTDROP);
+        }
+        else if (dragSource && cursor)
+            cursor->SetShape(dragElement == element ? CS_ACCEPTDROP : CS_REJECTDROP);
+
+        ++i;
+    }
+
+    // Hover effect
+    // If no drag is going on, transmit hover event.
+    if (element && element->IsEnabled())
+    {
+        if (dragElementsCount_ == 0)
+        {
+            element->OnHover(element->ScreenToElement(cursorPos), cursorPos, buttons, qualifiers, cursor);
+
+            // Begin hover event
+            if (!hoveredElements_.Contains(element))
+            {
+                SendDragOrHoverEvent(E_HOVERBEGIN, element, cursorPos, IntVector2::ZERO, 0);
+                // Exit if element is destroyed by the event handling
+                if (!element)
+                    return;
+            }
+            hoveredElements_[element] = true;
+        }
+    }
+}
+
+void SystemUI::ProcessClickBegin(const IntVector2& cursorPos, int button, int buttons, int qualifiers, Cursor* cursor, bool cursorVisible)
+{
+    if (cursorVisible)
+    {
+        WeakPtr<UIElement> element(GetElementAt(cursorPos));
+
+        bool newButton;
+        if (usingTouchInput_)
+            newButton = (button & buttons) == 0;
+        else
+            newButton = true;
+        buttons |= button;
+
+        if (element)
+            SetFocusElement(element);
+
+        // Focus change events may destroy the element, check again.
+        if (element)
+        {
+            // Handle focusing & bringing to front
+            element->BringToFront();
+
+            // Handle click
+            element->OnClickBegin(element->ScreenToElement(cursorPos), cursorPos, button, buttons, qualifiers, cursor);
+            SendClickEvent(E_UIMOUSECLICK, NULL, element, cursorPos, button, buttons, qualifiers);
+
+            // Fire double click event if element matches and is in time
+            if (doubleClickElement_ && element == doubleClickElement_ &&
+                clickTimer_.GetMSec(true) < (unsigned)(doubleClickInterval_ * 1000) && lastMouseButtons_ == buttons)
+            {
+                element->OnDoubleClick(element->ScreenToElement(cursorPos), cursorPos, button, buttons, qualifiers, cursor);
+                doubleClickElement_.Reset();
+                SendClickEvent(E_UIMOUSEDOUBLECLICK, NULL, element, cursorPos, button, buttons, qualifiers);
+            }
+            else
+            {
+                doubleClickElement_ = element;
+                clickTimer_.Reset();
+            }
+
+            // Handle start of drag. Click handling may have caused destruction of the element, so check the pointer again
+            bool dragElementsContain = dragElements_.Contains(element);
+            if (element && !dragElementsContain)
+            {
+                DragData* dragData = new DragData();
+                dragElements_[element] = dragData;
+                dragData->dragBeginPending = true;
+                dragData->sumPos = cursorPos;
+                dragData->dragBeginSumPos = cursorPos;
+                dragData->dragBeginTimer.Reset();
+                dragData->dragButtons = button;
+                dragData->numDragButtons = CountSetBits((unsigned)dragData->dragButtons);
+                dragElementsCount_++;
+
+                dragElementsContain = dragElements_.Contains(element);
+            }
+            else if (element && dragElementsContain && newButton)
+            {
+                DragData* dragData = dragElements_[element];
+                dragData->sumPos += cursorPos;
+                dragData->dragBeginSumPos += cursorPos;
+                dragData->dragButtons |= button;
+                dragData->numDragButtons = CountSetBits((unsigned)dragData->dragButtons);
+            }
+        }
+        else
+        {
+            // If clicked over no element, or a disabled element, lose focus (but not if there is a modal element)
+            if (!HasModalElement())
+                SetFocusElement(0);
+            SendClickEvent(E_UIMOUSECLICK, NULL, element, cursorPos, button, buttons, qualifiers);
+        }
+
+        lastMouseButtons_ = buttons;
+    }
+}
+
+void SystemUI::ProcessClickEnd(const IntVector2& cursorPos, int button, int buttons, int qualifiers, Cursor* cursor, bool cursorVisible)
+{
+    if (cursorVisible)
+    {
+        WeakPtr<UIElement> element(GetElementAt(cursorPos));
+
+        // Handle end of drag
+        for (HashMap<WeakPtr<UIElement>, SystemUI::DragData*>::Iterator i = dragElements_.Begin(); i != dragElements_.End();)
+        {
+            WeakPtr<UIElement> dragElement = i->first_;
+            SystemUI::DragData* dragData = i->second_;
+
+            if (!dragElement)
+            {
+                i = DragElementErase(i);
+                continue;
+            }
+
+            if (dragData->dragButtons & button)
+            {
+                // Handle end of click
+                if (element)
+                    element->OnClickEnd(element->ScreenToElement(cursorPos), cursorPos, button, buttons, qualifiers, cursor,
+                        dragElement);
+
+                SendClickEvent(E_UIMOUSECLICKEND, dragElement, element, cursorPos, button, buttons, qualifiers);
+
+                if (dragElement && dragElement->IsEnabled() && dragElement->IsVisible() && !dragData->dragBeginPending)
+                {
+                    dragElement->OnDragEnd(dragElement->ScreenToElement(cursorPos), cursorPos, dragData->dragButtons, buttons,
+                        cursor);
+                    SendDragOrHoverEvent(E_DRAGEND, dragElement, cursorPos, IntVector2::ZERO, dragData);
+
+                    bool dragSource = dragElement && (dragElement->GetDragDropMode() & DD_SOURCE) != 0;
+                    if (dragSource)
+                    {
+                        bool dragTarget = element && (element->GetDragDropMode() & DD_TARGET) != 0;
+                        bool dragDropFinish = dragSource && dragTarget && element != dragElement;
+
+                        if (dragDropFinish)
+                        {
+                            bool accept = element->OnDragDropFinish(dragElement);
+
+                            // OnDragDropFinish() may have caused destruction of the elements, so check the pointers again
+                            if (accept && dragElement && element)
+                            {
+                                using namespace DragDropFinish;
+
+                                VariantMap& eventData = GetEventDataMap();
+                                eventData[P_SOURCE] = dragElement.Get();
+                                eventData[P_TARGET] = element.Get();
+                                eventData[P_ACCEPT] = accept;
+                                SendEvent(E_DRAGDROPFINISH, eventData);
+                            }
+                        }
+                    }
+                }
+
+                i = DragElementErase(i);
+            }
+            else
+                ++i;
+        }
+    }
+}
+
+void SystemUI::ProcessMove(const IntVector2& cursorPos, const IntVector2& cursorDeltaPos, int buttons, int qualifiers, Cursor* cursor,
+    bool cursorVisible)
+{
+    if (cursorVisible && dragElementsCount_ > 0 && buttons)
+    {
+        Input* input = GetSubsystem<Input>();
+        bool mouseGrabbed = input->IsMouseGrabbed();
+        for (HashMap<WeakPtr<UIElement>, SystemUI::DragData*>::Iterator i = dragElements_.Begin(); i != dragElements_.End();)
+        {
+            WeakPtr<UIElement> dragElement = i->first_;
+            SystemUI::DragData* dragData = i->second_;
+
+            if (!dragElement)
+            {
+                i = DragElementErase(i);
+                continue;
+            }
+
+            if (!(dragData->dragButtons & buttons))
+            {
+                ++i;
+                continue;
+            }
+
+            // Calculate the position that we should send for this drag event.
+            IntVector2 sendPos;
+            if (usingTouchInput_)
+            {
+                dragData->sumPos += cursorDeltaPos;
+                sendPos.x_ = dragData->sumPos.x_ / dragData->numDragButtons;
+                sendPos.y_ = dragData->sumPos.y_ / dragData->numDragButtons;
+            }
+            else
+            {
+                dragData->sumPos = cursorPos;
+                sendPos = cursorPos;
+            }
+
+            if (dragElement->IsEnabled() && dragElement->IsVisible())
+            {
+                // Signal drag begin if distance threshold was exceeded
+
+                if (dragData->dragBeginPending && !mouseGrabbed)
+                {
+                    IntVector2 beginSendPos;
+                    beginSendPos.x_ = dragData->dragBeginSumPos.x_ / dragData->numDragButtons;
+                    beginSendPos.y_ = dragData->dragBeginSumPos.y_ / dragData->numDragButtons;
+
+                    IntVector2 offset = cursorPos - beginSendPos;
+                    if (Abs(offset.x_) >= dragBeginDistance_ || Abs(offset.y_) >= dragBeginDistance_)
+                    {
+                        dragData->dragBeginPending = false;
+                        dragConfirmedCount_++;
+                        dragElement->OnDragBegin(dragElement->ScreenToElement(beginSendPos), beginSendPos, buttons, qualifiers,
+                            cursor);
+                        SendDragOrHoverEvent(E_DRAGBEGIN, dragElement, beginSendPos, IntVector2::ZERO, dragData);
+                    }
+                }
+
+                if (!dragData->dragBeginPending)
+                {
+                    dragElement->OnDragMove(dragElement->ScreenToElement(sendPos), sendPos, cursorDeltaPos, buttons, qualifiers,
+                        cursor);
+                    SendDragOrHoverEvent(E_DRAGMOVE, dragElement, sendPos, cursorDeltaPos, dragData);
+                }
+            }
+            else
+            {
+                dragElement->OnDragEnd(dragElement->ScreenToElement(sendPos), sendPos, dragData->dragButtons, buttons, cursor);
+                SendDragOrHoverEvent(E_DRAGEND, dragElement, sendPos, IntVector2::ZERO, dragData);
+                dragElement.Reset();
+            }
+
+            ++i;
+        }
+    }
+}
+
+void SystemUI::SendDragOrHoverEvent(StringHash eventType, UIElement* element, const IntVector2& screenPos, const IntVector2& deltaPos,
+    SystemUI::DragData* dragData)
+{
+    if (!element)
+        return;
+
+    IntVector2 relativePos = element->ScreenToElement(screenPos);
+
+    using namespace DragMove;
+
+    VariantMap& eventData = GetEventDataMap();
+    eventData[P_ELEMENT] = element;
+    eventData[P_X] = screenPos.x_;
+    eventData[P_Y] = screenPos.y_;
+    eventData[P_ELEMENTX] = relativePos.x_;
+    eventData[P_ELEMENTY] = relativePos.y_;
+
+    if (eventType == E_DRAGMOVE)
+    {
+        eventData[P_DX] = deltaPos.x_;
+        eventData[P_DY] = deltaPos.y_;
+    }
+
+    if (dragData)
+    {
+        eventData[P_BUTTONS] = dragData->dragButtons;
+        eventData[P_NUMBUTTONS] = dragData->numDragButtons;
+    }
+
+    element->SendEvent(eventType, eventData);
+}
+
+void SystemUI::SendClickEvent(StringHash eventType, UIElement* beginElement, UIElement* endElement, const IntVector2& pos, int button,
+    int buttons, int qualifiers)
+{
+    VariantMap& eventData = GetEventDataMap();
+    eventData[UIMouseClick::P_ELEMENT] = endElement;
+    eventData[UIMouseClick::P_X] = pos.x_;
+    eventData[UIMouseClick::P_Y] = pos.y_;
+    eventData[UIMouseClick::P_BUTTON] = button;
+    eventData[UIMouseClick::P_BUTTONS] = buttons;
+    eventData[UIMouseClick::P_QUALIFIERS] = qualifiers;
+
+    // For click end events, send also the element the click began on
+    if (eventType == E_UIMOUSECLICKEND)
+        eventData[UIMouseClickEnd::P_BEGINELEMENT] = beginElement;
+
+    SendEvent(eventType, eventData);
+}
+
+void SystemUI::HandleScreenMode(StringHash eventType, VariantMap& eventData)
+{
+    using namespace ScreenMode;
+
+    if (!initialized_)
+        Initialize();
+    else
+    {
+        rootElement_->SetSize(eventData[P_WIDTH].GetInt(), eventData[P_HEIGHT].GetInt());
+        rootModalElement_->SetSize(rootElement_->GetSize());
+    }
+}
+
+void SystemUI::HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
+{
+    using namespace MouseButtonDown;
+
+    mouseButtons_ = eventData[P_BUTTONS].GetInt();
+    qualifiers_ = eventData[P_QUALIFIERS].GetInt();
+    usingTouchInput_ = false;
+
+    IntVector2 cursorPos;
+    bool cursorVisible;
+    GetCursorPositionAndVisible(cursorPos, cursorVisible);
+
+    // Handle drag cancelling
+    ProcessDragCancel();
+
+    Input* input = GetSubsystem<Input>();
+
+    if (!input->IsMouseGrabbed())
+        ProcessClickBegin(cursorPos, eventData[P_BUTTON].GetInt(), mouseButtons_, qualifiers_, cursor_, cursorVisible);
+}
+
+void SystemUI::HandleMouseButtonUp(StringHash eventType, VariantMap& eventData)
+{
+    using namespace MouseButtonUp;
+
+    mouseButtons_ = eventData[P_BUTTONS].GetInt();
+    qualifiers_ = eventData[P_QUALIFIERS].GetInt();
+
+    IntVector2 cursorPos;
+    bool cursorVisible;
+    GetCursorPositionAndVisible(cursorPos, cursorVisible);
+
+    ProcessClickEnd(cursorPos, eventData[P_BUTTON].GetInt(), mouseButtons_, qualifiers_, cursor_, cursorVisible);
+}
+
+void SystemUI::HandleMouseMove(StringHash eventType, VariantMap& eventData)
+{
+    using namespace MouseMove;
+
+    mouseButtons_ = eventData[P_BUTTONS].GetInt();
+    qualifiers_ = eventData[P_QUALIFIERS].GetInt();
+    usingTouchInput_ = false;
+
+    Input* input = GetSubsystem<Input>();
+    const IntVector2& rootSize = rootElement_->GetSize();
+
+    IntVector2 DeltaP = IntVector2(eventData[P_DX].GetInt(), eventData[P_DY].GetInt());
+
+    if (cursor_)
+    {
+        if (!input->IsMouseVisible())
+        {
+            // Relative mouse motion: move cursor only when visible
+            if (cursor_->IsVisible())
+            {
+                IntVector2 pos = cursor_->GetPosition();
+                pos.x_ += eventData[P_DX].GetInt();
+                pos.y_ += eventData[P_DY].GetInt();
+                pos.x_ = Clamp(pos.x_, 0, rootSize.x_ - 1);
+                pos.y_ = Clamp(pos.y_, 0, rootSize.y_ - 1);
+                cursor_->SetPosition(pos);
+            }
+        }
+        else
+        {
+            // Absolute mouse motion: move always
+            cursor_->SetPosition(IntVector2(eventData[P_X].GetInt(), eventData[P_Y].GetInt()));
+        }
+    }
+
+    IntVector2 cursorPos;
+    bool cursorVisible;
+    GetCursorPositionAndVisible(cursorPos, cursorVisible);
+
+    ProcessMove(cursorPos, DeltaP, mouseButtons_, qualifiers_, cursor_, cursorVisible);
+}
+
+void SystemUI::HandleMouseWheel(StringHash eventType, VariantMap& eventData)
+{
+    Input* input = GetSubsystem<Input>();
+    if (input->IsMouseGrabbed())
+        return;
+
+    using namespace MouseWheel;
+
+    mouseButtons_ = eventData[P_BUTTONS].GetInt();
+    qualifiers_ = eventData[P_QUALIFIERS].GetInt();
+    int delta = eventData[P_WHEEL].GetInt();
+    usingTouchInput_ = false;
+
+    IntVector2 cursorPos;
+    bool cursorVisible;
+    GetCursorPositionAndVisible(cursorPos, cursorVisible);
+
+    UIElement* element;
+    if (!nonFocusedMouseWheel_ && (element = focusElement_))
+        element->OnWheel(delta, mouseButtons_, qualifiers_);
+    else
+    {
+        // If no element has actual focus or in non-focused mode, get the element at cursor
+        if (cursorVisible)
+        {
+            element = GetElementAt(cursorPos);
+            if (nonFocusedMouseWheel_)
+            {
+                // Going up the hierarchy chain to find element that could handle mouse wheel
+                while (element)
+                {
+                    if (element->GetType() == ListView::GetTypeStatic() ||
+                        element->GetType() == ScrollView::GetTypeStatic())
+                        break;
+                    element = element->GetParent();
+                }
+            }
+            else
+                // If the element itself is not focusable, search for a focusable parent,
+                // although the focusable element may not actually handle mouse wheel
+                element = GetFocusableElement(element);
+
+            if (element && (nonFocusedMouseWheel_ || element->GetFocusMode() >= FM_FOCUSABLE))
+                element->OnWheel(delta, mouseButtons_, qualifiers_);
+        }
+    }
+}
+
+void SystemUI::HandleTouchBegin(StringHash eventType, VariantMap& eventData)
+{
+    Input* input = GetSubsystem<Input>();
+    if (input->IsMouseGrabbed())
+        return;
+
+    using namespace TouchBegin;
+
+    IntVector2 pos(eventData[P_X].GetInt(), eventData[P_Y].GetInt());
+    usingTouchInput_ = true;
+
+    int touchId = TOUCHID_MASK(eventData[P_TOUCHID].GetInt());
+    WeakPtr<UIElement> element(GetElementAt(pos));
+
+    if (element)
+    {
+        ProcessClickBegin(pos, touchId, touchDragElements_[element], 0, 0, true);
+        touchDragElements_[element] |= touchId;
+    }
+    else
+        ProcessClickBegin(pos, touchId, touchId, 0, 0, true);
+}
+
+void SystemUI::HandleTouchEnd(StringHash eventType, VariantMap& eventData)
+{
+    using namespace TouchEnd;
+
+    IntVector2 pos(eventData[P_X].GetInt(), eventData[P_Y].GetInt());
+
+    // Get the touch index
+    int touchId = TOUCHID_MASK(eventData[P_TOUCHID].GetInt());
+
+    // Transmit hover end to the position where the finger was lifted
+    WeakPtr<UIElement> element(GetElementAt(pos));
+
+    // Clear any drag events that were using the touch id
+    for (HashMap<WeakPtr<UIElement>, int>::Iterator i = touchDragElements_.Begin(); i != touchDragElements_.End();)
+    {
+        int touches = i->second_;
+        if (touches & touchId)
+            i = touchDragElements_.Erase(i);
+        else
+            ++i;
+    }
+
+    if (element && element->IsEnabled())
+        element->OnHover(element->ScreenToElement(pos), pos, 0, 0, 0);
+
+    ProcessClickEnd(pos, touchId, 0, 0, 0, true);
+}
+
+void SystemUI::HandleTouchMove(StringHash eventType, VariantMap& eventData)
+{
+    using namespace TouchMove;
+
+    IntVector2 pos(eventData[P_X].GetInt(), eventData[P_Y].GetInt());
+    IntVector2 deltaPos(eventData[P_DX].GetInt(), eventData[P_DY].GetInt());
+    usingTouchInput_ = true;
+
+    int touchId = TOUCHID_MASK(eventData[P_TOUCHID].GetInt());
+
+    ProcessMove(pos, deltaPos, touchId, 0, 0, true);
+}
+
+void SystemUI::HandleKeyDown(StringHash eventType, VariantMap& eventData)
+{
+    using namespace KeyDown;
+
+    mouseButtons_ = eventData[P_BUTTONS].GetInt();
+    qualifiers_ = eventData[P_QUALIFIERS].GetInt();
+    int key = eventData[P_KEY].GetInt();
+
+    // Cancel UI dragging
+    if (key == KEY_ESC && dragElementsCount_ > 0)
+    {
+        ProcessDragCancel();
+
+        return;
+    }
+
+    // Dismiss modal element if any when ESC key is pressed
+    if (key == KEY_ESC && HasModalElement())
+    {
+        UIElement* element = rootModalElement_->GetChild(rootModalElement_->GetNumChildren() - 1);
+        if (element->GetVars().Contains(VAR_ORIGIN))
+            // If it is a popup, dismiss by defocusing it
+            SetFocusElement(0);
+        else
+        {
+            // If it is a modal window, by resetting its modal flag
+            Window* window = dynamic_cast<Window*>(element);
+            if (window && window->GetModalAutoDismiss())
+                window->SetModal(false);
+        }
+
+        return;
+    }
+
+    UIElement* element = focusElement_;
+    if (element)
+    {
+        // Switch focus between focusable elements in the same top level window
+        if (key == KEY_TAB)
+        {
+            UIElement* topLevel = element->GetParent();
+            while (topLevel && topLevel->GetParent() != rootElement_ && topLevel->GetParent() != rootModalElement_)
+                topLevel = topLevel->GetParent();
+            if (topLevel)
+            {
+                topLevel->GetChildren(tempElements_, true);
+                for (PODVector<UIElement*>::Iterator i = tempElements_.Begin(); i != tempElements_.End();)
+                {
+                    if ((*i)->GetFocusMode() < FM_FOCUSABLE)
+                        i = tempElements_.Erase(i);
+                    else
+                        ++i;
+                }
+                for (unsigned i = 0; i < tempElements_.Size(); ++i)
+                {
+                    if (tempElements_[i] == element)
+                    {
+                        int dir = (qualifiers_ & QUAL_SHIFT) ? -1 : 1;
+                        unsigned nextIndex = (tempElements_.Size() + i + dir) % tempElements_.Size();
+                        UIElement* next = tempElements_[nextIndex];
+                        SetFocusElement(next, true);
+                        return;
+                    }
+                }
+            }
+        }
+        // Defocus the element
+        else if (key == KEY_ESC && element->GetFocusMode() == FM_FOCUSABLE_DEFOCUSABLE)
+            element->SetFocus(false);
+        // If none of the special keys, pass the key to the focused element
+        else
+            element->OnKey(key, mouseButtons_, qualifiers_);
+    }
+}
+
+void SystemUI::HandleTextInput(StringHash eventType, VariantMap& eventData)
+{
+    using namespace TextInput;
+
+    mouseButtons_ = eventData[P_BUTTONS].GetInt();
+    qualifiers_ = eventData[P_QUALIFIERS].GetInt();
+
+    UIElement* element = focusElement_;
+    if (element)
+        element->OnTextInput(eventData[P_TEXT].GetString(), mouseButtons_, qualifiers_);
+}
+
+void SystemUI::HandleBeginFrame(StringHash eventType, VariantMap& eventData)
+{
+    // If have a cursor, and a drag is not going on, reset the cursor shape. Application logic that wants to apply
+    // custom shapes can do it after this, but needs to do it each frame
+    if (cursor_ && dragElementsCount_ == 0)
+        cursor_->SetShape(CS_NORMAL);
+}
+
+void SystemUI::HandlePostUpdate(StringHash eventType, VariantMap& eventData)
+{
+    using namespace PostUpdate;
+
+    Update(eventData[P_TIMESTEP].GetFloat());
+}
+
+void SystemUI::HandleRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+    RenderUpdate();
+}
+
+void SystemUI::HandleDropFile(StringHash eventType, VariantMap& eventData)
+{
+    Input* input = GetSubsystem<Input>();
+
+    // Sending the UI variant of the event only makes sense if the OS cursor is visible (not locked to window center)
+    if (input->IsMouseVisible())
+    {
+        IntVector2 screenPos = input->GetMousePosition();
+        UIElement* element = GetElementAt(screenPos);
+
+        using namespace UIDropFile;
+
+        VariantMap uiEventData;
+        uiEventData[P_FILENAME] = eventData[P_FILENAME];
+        uiEventData[P_X] = screenPos.x_;
+        uiEventData[P_Y] = screenPos.y_;
+        uiEventData[P_ELEMENT] = element;
+
+        if (element)
+        {
+            IntVector2 relativePos = element->ScreenToElement(screenPos);
+            uiEventData[P_ELEMENTX] = relativePos.x_;
+            uiEventData[P_ELEMENTY] = relativePos.y_;
+        }
+
+        SendEvent(E_UIDROPFILE, uiEventData);
+    }
+}
+
+HashMap<WeakPtr<UIElement>, SystemUI::DragData*>::Iterator SystemUI::DragElementErase(HashMap<WeakPtr<UIElement>, SystemUI::DragData*>::Iterator i)
+{
+    // If running the engine frame in response to an event (re-entering UI frame logic) the dragElements_ may already be empty
+    if (dragElements_.Empty())
+        return dragElements_.End();
+
+    dragElementsConfirmed_.Clear();
+
+    DragData* dragData = i->second_;
+
+    if (!dragData->dragBeginPending)
+        --dragConfirmedCount_;
+    i = dragElements_.Erase(i);
+    --dragElementsCount_;
+
+    delete dragData;
+    return i;
+}
+
+void SystemUI::ProcessDragCancel()
+{
+    // How to tell difference between drag cancel and new selection on multi-touch?
+    if (usingTouchInput_)
+        return;
+
+    IntVector2 cursorPos;
+    bool cursorVisible;
+    GetCursorPositionAndVisible(cursorPos, cursorVisible);
+
+    for (HashMap<WeakPtr<UIElement>, SystemUI::DragData*>::Iterator i = dragElements_.Begin(); i != dragElements_.End();)
+    {
+        WeakPtr<UIElement> dragElement = i->first_;
+        SystemUI::DragData* dragData = i->second_;
+
+        if (dragElement && dragElement->IsEnabled() && dragElement->IsVisible() && !dragData->dragBeginPending)
+        {
+            dragElement->OnDragCancel(dragElement->ScreenToElement(cursorPos), cursorPos, dragData->dragButtons, mouseButtons_,
+                cursor_);
+            SendDragOrHoverEvent(E_DRAGCANCEL, dragElement, cursorPos, IntVector2::ZERO, dragData);
+            i = DragElementErase(i);
+        }
+        else
+            ++i;
+    }
+}
+
+IntVector2 SystemUI::SumTouchPositions(SystemUI::DragData* dragData, const IntVector2& oldSendPos)
+{
+    IntVector2 sendPos = oldSendPos;
+    if (usingTouchInput_)
+    {
+        int buttons = dragData->dragButtons;
+        dragData->sumPos = IntVector2::ZERO;
+        Input* input = GetSubsystem<Input>();
+        for (int i = 0; (1 << i) <= buttons; i++)
+        {
+            if ((1 << i) & buttons)
+            {
+                TouchState* ts = input->GetTouch((unsigned)i);
+                if (!ts)
+                    break;
+                IntVector2 pos = ts->position_;
+                dragData->sumPos.x_ += pos.x_;
+                dragData->sumPos.y_ += pos.y_;
+            }
+        }
+        sendPos.x_ = dragData->sumPos.x_ / dragData->numDragButtons;
+        sendPos.y_ = dragData->sumPos.y_ / dragData->numDragButtons;
+    }
+    return sendPos;
+}
+
+void SystemUI::CreateConsoleAndDebugHud()
+{
+    // Get default style
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    XMLFile* xmlFile = cache->GetResource<XMLFile>("UI/DefaultStyle.xml");
+
+    Console* console = new Console(context_);
+    console->SetDefaultStyle(xmlFile);
+    console->GetBackground()->SetOpacity(0.8f);
+
+    DebugHud* debugHud = new DebugHud(context_);
+    debugHud->SetDefaultStyle(xmlFile);
+
+    // Create console & debug hud
+    context_->RegisterSubsystem(console);
+    context_->RegisterSubsystem(debugHud);
+
+}
+
+void RegisterSystemUILibrary(Context* context)
+{
+    Font::RegisterObject(context);
+
+    UIElement::RegisterObject(context);
+    BorderImage::RegisterObject(context);
+    Sprite::RegisterObject(context);
+    Button::RegisterObject(context);
+    CheckBox::RegisterObject(context);
+    Cursor::RegisterObject(context);
+    Text::RegisterObject(context);
+    Window::RegisterObject(context);
+    LineEdit::RegisterObject(context);
+    Slider::RegisterObject(context);
+    ScrollBar::RegisterObject(context);
+    ScrollView::RegisterObject(context);
+    ListView::RegisterObject(context);
+    Menu::RegisterObject(context);
+    DropDownList::RegisterObject(context);
+    MessageBox::RegisterObject(context);
+    ToolTip::RegisterObject(context);
+}
+
+}
+
+}

+ 347 - 0
Source/Atomic/UI/SystemUI/SystemUI.h

@@ -0,0 +1,347 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "../../Core/Object.h"
+#include "Cursor.h"
+#include "SystemUIBatch.h"
+
+namespace Atomic
+{
+
+class Graphics;
+class ResourceCache;
+class Timer;
+class VertexBuffer;
+class XMLElement;
+class XMLFile;
+
+namespace SystemUI
+{
+
+class Cursor;
+class SystemUIBatch;
+class UIElement;
+
+/// %UI subsystem. Manages the graphical user interface.
+class ATOMIC_API SystemUI : public Object
+{
+    OBJECT(SystemUI);
+
+public:
+    /// Construct.
+    SystemUI(Context* context);
+    /// Destruct.
+    virtual ~SystemUI();
+
+    /// Set cursor UI element.
+    void SetCursor(Cursor* cursor);
+    /// Set focused UI element.
+    void SetFocusElement(UIElement* element, bool byKey = false);
+    /// Set modal element. Until all the modal elements are dismissed, all the inputs and events are only sent to them. Return true when successful.
+    /// Only the modal element can clear its modal status or when it is being destructed.
+    bool SetModalElement(UIElement* modalElement, bool enable);
+    /// Clear the UI (excluding the cursor.)
+    void Clear();
+    /// Update the UI logic. Called by HandlePostUpdate().
+    void Update(float timeStep);
+    /// Update the UI for rendering. Called by HandleRenderUpdate().
+    void RenderUpdate();
+    /// Render the UI. If resetRenderTargets is true, is assumed to be the default UI render to backbuffer called by Engine, and will be performed only once. Additional UI renders to a different rendertarget may be triggered from the renderpath.
+    void Render(bool resetRenderTargets = true);
+    /// Debug draw a UI element.
+    void DebugDraw(UIElement* element);
+    /// Load a UI layout from an XML file. Optionally specify another XML file for element style. Return the root element.
+    SharedPtr<UIElement> LoadLayout(Deserializer& source, XMLFile* styleFile = 0);
+    /// Load a UI layout from an XML file. Optionally specify another XML file for element style. Return the root element.
+    SharedPtr<UIElement> LoadLayout(XMLFile* file, XMLFile* styleFile = 0);
+    /// Save a UI layout to an XML file. Return true if successful.
+    bool SaveLayout(Serializer& dest, UIElement* element);
+    /// Set clipboard text.
+    void SetClipboardText(const String& text);
+    /// Set UI element double click interval in seconds.
+    void SetDoubleClickInterval(float interval);
+    /// Set UI drag event start interval in seconds.
+    void SetDragBeginInterval(float interval);
+    /// Set UI drag event start distance threshold in pixels.
+    void SetDragBeginDistance(int pixels);
+    /// Set tooltip default display delay in seconds.
+    void SetDefaultToolTipDelay(float delay);
+    /// Set maximum font face texture size. Must be a power of two. Default is 2048.
+    void SetMaxFontTextureSize(int size);
+    /// Set whether mouse wheel can control also a non-focused element.
+    void SetNonFocusedMouseWheel(bool nonFocusedMouseWheel);
+    /// Set whether to use system clipboard. Default false.
+    void SetUseSystemClipboard(bool enable);
+    /// Set whether to show the on-screen keyboard (if supported) when a %LineEdit is focused. Default true on mobile devices.
+    void SetUseScreenKeyboard(bool enable);
+    /// Set whether to use mutable (eraseable) glyphs to ensure a font face never expands to more than one texture. Default false.
+    void SetUseMutableGlyphs(bool enable);
+    /// Set whether to force font autohinting instead of using FreeType's TTF bytecode interpreter.
+    void SetForceAutoHint(bool enable);
+
+    /// Return root UI element.
+    UIElement* GetRoot() const { return rootElement_; }
+
+    /// Return root modal element.
+    UIElement* GetRootModalElement() const { return rootModalElement_; }
+
+    /// Return cursor.
+    Cursor* GetCursor() const { return cursor_; }
+
+    /// Return cursor position.
+    IntVector2 GetCursorPosition() const;
+    /// Return UI element at screen coordinates. By default returns only input-enabled elements.
+    UIElement* GetElementAt(const IntVector2& position, bool enabledOnly = true);
+    /// Return UI element at screen coordinates. By default returns only input-enabled elements.
+    UIElement* GetElementAt(int x, int y, bool enabledOnly = true);
+
+    /// Return focused element.
+    UIElement* GetFocusElement() const { return focusElement_; }
+
+    /// Return topmost enabled root-level non-modal element.
+    UIElement* GetFrontElement() const;
+    /// Return currently dragged elements.
+    const Vector<UIElement*> GetDragElements();
+
+    /// Return the number of currently dragged elements.
+    unsigned GetNumDragElements() const { return (unsigned)dragConfirmedCount_; }
+
+    /// Return the drag element at index.
+    UIElement* GetDragElement(unsigned index);
+    /// Return clipboard text.
+    const String& GetClipboardText() const;
+
+    /// Return UI element double click interval in seconds.
+    float GetDoubleClickInterval() const { return doubleClickInterval_; }
+
+    /// Return UI drag start event interval in seconds.
+    float GetDragBeginInterval() const { return dragBeginInterval_; }
+
+    /// Return UI drag start event distance threshold in pixels.
+    int GetDragBeginDistance() const { return dragBeginDistance_; }
+
+    /// Return tooltip default display delay in seconds.
+    float GetDefaultToolTipDelay() const { return defaultToolTipDelay_; }
+
+    /// Return font texture maximum size.
+    int GetMaxFontTextureSize() const { return maxFontTextureSize_; }
+
+    /// Return whether mouse wheel can control also a non-focused element.
+    bool IsNonFocusedMouseWheel() const { return nonFocusedMouseWheel_; }
+
+    /// Return whether is using the system clipboard.
+    bool GetUseSystemClipboard() const { return useSystemClipboard_; }
+
+    /// Return whether focusing a %LineEdit will show the on-screen keyboard.
+    bool GetUseScreenKeyboard() const { return useScreenKeyboard_; }
+
+    /// Return whether is using mutable (eraseable) glyphs for fonts.
+    bool GetUseMutableGlyphs() const { return useMutableGlyphs_; }
+
+    /// Return whether is using forced autohinting.
+    bool GetForceAutoHint() const { return forceAutoHint_; }
+
+    /// Return true when UI has modal element(s).
+    bool HasModalElement() const;
+
+    /// Return whether a drag is in progress.
+    bool IsDragging() const { return dragConfirmedCount_ > 0; }
+
+    /// Data structure used to represent the drag data associated to a UIElement.
+    struct DragData
+    {
+        /// Which button combo initiated the drag.
+        int dragButtons;
+        /// How many buttons initiated the drag.
+        int numDragButtons;
+        /// Sum of all touch locations
+        IntVector2 sumPos;
+        /// Flag for a drag start event pending.
+        bool dragBeginPending;
+        /// Timer used to trigger drag begin event.
+        Timer dragBeginTimer;
+        /// Drag start position.
+        IntVector2 dragBeginSumPos;
+    };
+
+    void CreateConsoleAndDebugHud();
+
+private:
+    /// Initialize when screen mode initially set.
+    void Initialize();
+    /// Update UI element logic recursively.
+    void Update(float timeStep, UIElement* element);
+    /// Upload UI geometry into a vertex buffer.
+    void SetVertexData(VertexBuffer* dest, const PODVector<float>& vertexData);
+    /// Render UI batches. Geometry must have been uploaded first.
+    void Render
+        (bool resetRenderTargets, VertexBuffer* buffer, const PODVector<SystemUIBatch>& batches, unsigned batchStart, unsigned batchEnd);
+    /// Generate batches from an UI element recursively. Skip the cursor element.
+    void GetBatches(UIElement* element, IntRect currentScissor);
+    /// Return UI element at screen position recursively.
+    void GetElementAt(UIElement*& result, UIElement* current, const IntVector2& position, bool enabledOnly);
+    /// Return the first element in hierarchy that can alter focus.
+    UIElement* GetFocusableElement(UIElement* element);
+    /// Return cursor position and visibility either from the cursor element, or the Input subsystem.
+    void GetCursorPositionAndVisible(IntVector2& pos, bool& visible);
+    /// Set active cursor's shape.
+    void SetCursorShape(CursorShape shape);
+    /// Force release of font faces when global font properties change.
+    void ReleaseFontFaces();
+    /// Handle button or touch hover
+    void ProcessHover(const IntVector2& cursorPos, int buttons, int qualifiers, Cursor* cursor);
+    /// Handle button or touch begin.
+    void
+        ProcessClickBegin(const IntVector2& cursorPos, int button, int buttons, int qualifiers, Cursor* cursor, bool cursorVisible);
+    /// Handle button or touch end.
+    void ProcessClickEnd(const IntVector2& cursorPos, int button, int buttons, int qualifiers, Cursor* cursor, bool cursorVisible);
+    /// Handle mouse or touch move.
+    void ProcessMove(const IntVector2& cursorPos, const IntVector2& cursorDeltaPos, int buttons, int qualifiers, Cursor* cursor,
+        bool cursorVisible);
+    /// Send a UI element drag or hover begin event.
+    void SendDragOrHoverEvent
+        (StringHash eventType, UIElement* element, const IntVector2& screenPos, const IntVector2& deltaPos, SystemUI::DragData* dragData);
+    /// Send a UI click or double click event.
+    void SendClickEvent
+        (StringHash eventType, UIElement* beginElement, UIElement* endElement, const IntVector2& pos, int button, int buttons,
+            int qualifiers);
+    /// Handle screen mode event.
+    void HandleScreenMode(StringHash eventType, VariantMap& eventData);
+    /// Handle mouse button down event.
+    void HandleMouseButtonDown(StringHash eventType, VariantMap& eventData);
+    /// Handle mouse button up event.
+    void HandleMouseButtonUp(StringHash eventType, VariantMap& eventData);
+    /// Handle mouse move event.
+    void HandleMouseMove(StringHash eventType, VariantMap& eventData);
+    /// Handle mouse wheel event.
+    void HandleMouseWheel(StringHash eventType, VariantMap& eventData);
+    /// Handle touch begin event.
+    void HandleTouchBegin(StringHash eventType, VariantMap& eventData);
+    /// Handle touch end event.
+    void HandleTouchEnd(StringHash eventType, VariantMap& eventData);
+    /// Handle touch move event.
+    void HandleTouchMove(StringHash eventType, VariantMap& eventData);
+    /// Handle keypress event.
+    void HandleKeyDown(StringHash eventType, VariantMap& eventData);
+    /// Handle text input event.
+    void HandleTextInput(StringHash eventType, VariantMap& eventData);
+    /// Handle frame begin event.
+    void HandleBeginFrame(StringHash eventType, VariantMap& eventData);
+    /// Handle logic post-update event.
+    void HandlePostUpdate(StringHash eventType, VariantMap& eventData);
+    /// Handle render update event.
+    void HandleRenderUpdate(StringHash eventType, VariantMap& eventData);
+    /// Handle a file being drag-dropped into the application window.
+    void HandleDropFile(StringHash eventType, VariantMap& eventData);
+    /// Remove drag data and return next iterator.
+    HashMap<WeakPtr<UIElement>, DragData*>::Iterator DragElementErase(HashMap<WeakPtr<UIElement>, DragData*>::Iterator dragElement);
+    /// Handle clean up on a drag cancel.
+    void ProcessDragCancel();
+    /// Sum touch positions and return the begin position ready to send.
+    IntVector2 SumTouchPositions(SystemUI::DragData* dragData, const IntVector2& oldSendPos);
+
+    /// Graphics subsystem.
+    WeakPtr<Graphics> graphics_;
+    /// UI root element.
+    SharedPtr<UIElement> rootElement_;
+    /// UI root modal element.
+    SharedPtr<UIElement> rootModalElement_;
+    /// Cursor.
+    SharedPtr<Cursor> cursor_;
+    /// Currently focused element.
+    WeakPtr<UIElement> focusElement_;
+    /// UI rendering batches.
+    PODVector<SystemUIBatch> batches_;
+    /// UI rendering vertex data.
+    PODVector<float> vertexData_;
+    /// UI rendering batches for debug draw.
+    PODVector<SystemUIBatch> debugDrawBatches_;
+    /// UI rendering vertex data for debug draw.
+    PODVector<float> debugVertexData_;
+    /// UI vertex buffer.
+    SharedPtr<VertexBuffer> vertexBuffer_;
+    /// UI debug geometry vertex buffer.
+    SharedPtr<VertexBuffer> debugVertexBuffer_;
+    /// UI element query vector.
+    PODVector<UIElement*> tempElements_;
+    /// Clipboard text.
+    mutable String clipBoard_;
+    /// Seconds between clicks to register a double click.
+    float doubleClickInterval_;
+    /// Seconds from mouse button down to begin a drag if there has been no movement exceeding pixel threshold.
+    float dragBeginInterval_;
+    /// Tooltip default display delay in seconds.
+    float defaultToolTipDelay_;
+    /// Drag begin event distance threshold in pixels.
+    int dragBeginDistance_;
+    /// Mouse buttons held down.
+    int mouseButtons_;
+    /// Last mouse button pressed.
+    int lastMouseButtons_;
+    /// Qualifier keys held down.
+    int qualifiers_;
+    /// Font texture maximum size.
+    int maxFontTextureSize_;
+    /// Initialized flag.
+    bool initialized_;
+    /// Touch used flag.
+    bool usingTouchInput_;
+    /// Flag to switch mouse wheel event to be sent to non-focused element at cursor.
+    bool nonFocusedMouseWheel_;
+    /// Flag for using operating system clipboard instead of internal.
+    bool useSystemClipboard_;
+    /// Flag for showing the on-screen keyboard on focusing a %LineEdit.
+    bool useScreenKeyboard_;
+    /// Flag for using mutable (erasable) font glyphs.
+    bool useMutableGlyphs_;
+    /// Flag for forcing FreeType auto hinting.
+    bool forceAutoHint_;
+    /// Flag for UI already being rendered this frame.
+    bool uiRendered_;
+    /// Non-modal batch size (used internally for rendering).
+    unsigned nonModalBatchSize_;
+    /// Timer used to trigger double click.
+    Timer clickTimer_;
+    /// UI element last clicked for tracking double clicks.
+    WeakPtr<UIElement> doubleClickElement_;
+    /// Currently hovered elements.
+    HashMap<WeakPtr<UIElement>, bool> hoveredElements_;
+    /// Currently dragged elements.
+    HashMap<WeakPtr<UIElement>, DragData*> dragElements_;
+    /// Number of elements in dragElements_.
+    int dragElementsCount_;
+    /// Number of elements in dragElements_ with dragPending = false.
+    int dragConfirmedCount_;
+    /// UI elements that are being touched with touch input.
+    HashMap<WeakPtr<UIElement>, int> touchDragElements_;
+    /// Confirmed drag elements cache.
+    Vector<UIElement*> dragElementsConfirmed_;
+};
+
+/// Register UI library objects.
+void ATOMIC_API RegisterSystemUILibrary(Context* context);
+
+}
+
+}

+ 334 - 0
Source/Atomic/UI/SystemUI/SystemUIBatch.cpp

@@ -0,0 +1,334 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Graphics/Graphics.h"
+#include "../../Graphics/Texture.h"
+#include "UIElement.h"
+
+#include "../../DebugNew.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+Vector3 SystemUIBatch::posAdjust(0.0f, 0.0f, 0.0f);
+
+SystemUIBatch::SystemUIBatch() :
+    element_(0),
+    blendMode_(BLEND_REPLACE),
+    texture_(0),
+    invTextureSize_(Vector2::ONE),
+    vertexData_(0),
+    vertexStart_(0),
+    vertexEnd_(0)
+{
+    SetDefaultColor();
+}
+
+SystemUIBatch::SystemUIBatch(UIElement* element, BlendMode blendMode, const IntRect& scissor, Texture* texture, PODVector<float>* vertexData) :
+    element_(element),
+    blendMode_(blendMode),
+    scissor_(scissor),
+    texture_(texture),
+    invTextureSize_(texture ? Vector2(1.0f / (float)texture->GetWidth(), 1.0f / (float)texture->GetHeight()) : Vector2::ONE),
+    vertexData_(vertexData),
+    vertexStart_(vertexData->Size()),
+    vertexEnd_(vertexData->Size())
+{
+    SetDefaultColor();
+}
+
+void SystemUIBatch::SetColor(const Color& color, bool overrideAlpha)
+{
+    if (!element_)
+        overrideAlpha = true;
+
+    useGradient_ = false;
+    color_ =
+        overrideAlpha ? color.ToUInt() : Color(color.r_, color.g_, color.b_, color.a_ * element_->GetDerivedOpacity()).ToUInt();
+}
+
+void SystemUIBatch::SetDefaultColor()
+{
+    if (element_)
+    {
+        color_ = element_->GetDerivedColor().ToUInt();
+        useGradient_ = element_->HasColorGradient();
+    }
+    else
+    {
+        color_ = 0xffffffff;
+        useGradient_ = false;
+    }
+}
+
+void SystemUIBatch::AddQuad(int x, int y, int width, int height, int texOffsetX, int texOffsetY, int texWidth, int texHeight)
+{
+    unsigned topLeftColor, topRightColor, bottomLeftColor, bottomRightColor;
+
+    if (!useGradient_)
+    {
+        // If alpha is 0, nothing will be rendered, so do not add the quad
+        if (!(color_ & 0xff000000))
+            return;
+
+        topLeftColor = color_;
+        topRightColor = color_;
+        bottomLeftColor = color_;
+        bottomRightColor = color_;
+    }
+    else
+    {
+        topLeftColor = GetInterpolatedColor(x, y);
+        topRightColor = GetInterpolatedColor(x + width, y);
+        bottomLeftColor = GetInterpolatedColor(x, y + height);
+        bottomRightColor = GetInterpolatedColor(x + width, y + height);
+    }
+
+    const IntVector2& screenPos = element_->GetScreenPosition();
+
+    float left = (float)(x + screenPos.x_) - posAdjust.x_;
+    float right = left + (float)width;
+    float top = (float)(y + screenPos.y_) - posAdjust.x_;
+    float bottom = top + (float)height;
+
+    float leftUV = texOffsetX * invTextureSize_.x_;
+    float topUV = texOffsetY * invTextureSize_.y_;
+    float rightUV = (texOffsetX + (texWidth ? texWidth : width)) * invTextureSize_.x_;
+    float bottomUV = (texOffsetY + (texHeight ? texHeight : height)) * invTextureSize_.y_;
+
+    unsigned begin = vertexData_->Size();
+    vertexData_->Resize(begin + 6 * UI_VERTEX_SIZE);
+    float* dest = &(vertexData_->At(begin));
+    vertexEnd_ = vertexData_->Size();
+
+    dest[0] = left;
+    dest[1] = top;
+    dest[2] = 0.0f;
+    ((unsigned&)dest[3]) = topLeftColor;
+    dest[4] = leftUV;
+    dest[5] = topUV;
+
+    dest[6] = right;
+    dest[7] = top;
+    dest[8] = 0.0f;
+    ((unsigned&)dest[9]) = topRightColor;
+    dest[10] = rightUV;
+    dest[11] = topUV;
+
+    dest[12] = left;
+    dest[13] = bottom;
+    dest[14] = 0.0f;
+    ((unsigned&)dest[15]) = bottomLeftColor;
+    dest[16] = leftUV;
+    dest[17] = bottomUV;
+
+    dest[18] = right;
+    dest[19] = top;
+    dest[20] = 0.0f;
+    ((unsigned&)dest[21]) = topRightColor;
+    dest[22] = rightUV;
+    dest[23] = topUV;
+
+    dest[24] = right;
+    dest[25] = bottom;
+    dest[26] = 0.0f;
+    ((unsigned&)dest[27]) = bottomRightColor;
+    dest[28] = rightUV;
+    dest[29] = bottomUV;
+
+    dest[30] = left;
+    dest[31] = bottom;
+    dest[32] = 0.0f;
+    ((unsigned&)dest[33]) = bottomLeftColor;
+    dest[34] = leftUV;
+    dest[35] = bottomUV;
+}
+
+void SystemUIBatch::AddQuad(const Matrix3x4& transform, int x, int y, int width, int height, int texOffsetX, int texOffsetY,
+    int texWidth, int texHeight)
+{
+    unsigned topLeftColor, topRightColor, bottomLeftColor, bottomRightColor;
+
+    if (!useGradient_)
+    {
+        // If alpha is 0, nothing will be rendered, so do not add the quad
+        if (!(color_ & 0xff000000))
+            return;
+
+        topLeftColor = color_;
+        topRightColor = color_;
+        bottomLeftColor = color_;
+        bottomRightColor = color_;
+    }
+    else
+    {
+        topLeftColor = GetInterpolatedColor(x, y);
+        topRightColor = GetInterpolatedColor(x + width, y);
+        bottomLeftColor = GetInterpolatedColor(x, y + height);
+        bottomRightColor = GetInterpolatedColor(x + width, y + height);
+    }
+
+    Vector3 v1 = (transform * Vector3((float)x, (float)y, 0.0f)) - posAdjust;
+    Vector3 v2 = (transform * Vector3((float)x + (float)width, (float)y, 0.0f)) - posAdjust;
+    Vector3 v3 = (transform * Vector3((float)x, (float)y + (float)height, 0.0f)) - posAdjust;
+    Vector3 v4 = (transform * Vector3((float)x + (float)width, (float)y + (float)height, 0.0f)) - posAdjust;
+
+    float leftUV = ((float)texOffsetX) * invTextureSize_.x_;
+    float topUV = ((float)texOffsetY) * invTextureSize_.y_;
+    float rightUV = ((float)(texOffsetX + (texWidth ? texWidth : width))) * invTextureSize_.x_;
+    float bottomUV = ((float)(texOffsetY + (texHeight ? texHeight : height))) * invTextureSize_.y_;
+
+    unsigned begin = vertexData_->Size();
+    vertexData_->Resize(begin + 6 * UI_VERTEX_SIZE);
+    float* dest = &(vertexData_->At(begin));
+    vertexEnd_ = vertexData_->Size();
+
+    dest[0] = v1.x_;
+    dest[1] = v1.y_;
+    dest[2] = 0.0f;
+    ((unsigned&)dest[3]) = topLeftColor;
+    dest[4] = leftUV;
+    dest[5] = topUV;
+
+    dest[6] = v2.x_;
+    dest[7] = v2.y_;
+    dest[8] = 0.0f;
+    ((unsigned&)dest[9]) = topRightColor;
+    dest[10] = rightUV;
+    dest[11] = topUV;
+
+    dest[12] = v3.x_;
+    dest[13] = v3.y_;
+    dest[14] = 0.0f;
+    ((unsigned&)dest[15]) = bottomLeftColor;
+    dest[16] = leftUV;
+    dest[17] = bottomUV;
+
+    dest[18] = v2.x_;
+    dest[19] = v2.y_;
+    dest[20] = 0.0f;
+    ((unsigned&)dest[21]) = topRightColor;
+    dest[22] = rightUV;
+    dest[23] = topUV;
+
+    dest[24] = v4.x_;
+    dest[25] = v4.y_;
+    dest[26] = 0.0f;
+    ((unsigned&)dest[27]) = bottomRightColor;
+    dest[28] = rightUV;
+    dest[29] = bottomUV;
+
+    dest[30] = v3.x_;
+    dest[31] = v3.y_;
+    dest[32] = 0.0f;
+    ((unsigned&)dest[33]) = bottomLeftColor;
+    dest[34] = leftUV;
+    dest[35] = bottomUV;
+}
+
+void SystemUIBatch::AddQuad(int x, int y, int width, int height, int texOffsetX, int texOffsetY, int texWidth, int texHeight, bool tiled)
+{
+    if (!(element_->HasColorGradient() || element_->GetDerivedColor().ToUInt() & 0xff000000))
+        return; // No gradient and alpha is 0, so do not add the quad
+
+    if (!tiled)
+    {
+        AddQuad(x, y, width, height, texOffsetX, texOffsetY, texWidth, texHeight);
+        return;
+    }
+
+    int tileX = 0;
+    int tileY = 0;
+    int tileW = 0;
+    int tileH = 0;
+
+    while (tileY < height)
+    {
+        tileX = 0;
+        tileH = Min(height - tileY, texHeight);
+
+        while (tileX < width)
+        {
+            tileW = Min(width - tileX, texWidth);
+
+            AddQuad(x + tileX, y + tileY, tileW, tileH, texOffsetX, texOffsetY, tileW, tileH);
+
+            tileX += tileW;
+        }
+
+        tileY += tileH;
+    }
+}
+
+bool SystemUIBatch::Merge(const SystemUIBatch& batch)
+{
+    if (batch.blendMode_ != blendMode_ ||
+        batch.scissor_ != scissor_ ||
+        batch.texture_ != texture_ ||
+        batch.vertexData_ != vertexData_ ||
+        batch.vertexStart_ != vertexEnd_)
+        return false;
+
+    vertexEnd_ = batch.vertexEnd_;
+    return true;
+}
+
+unsigned SystemUIBatch::GetInterpolatedColor(int x, int y)
+{
+    const IntVector2& size = element_->GetSize();
+
+    if (size.x_ && size.y_)
+    {
+        float cLerpX = Clamp((float)x / (float)size.x_, 0.0f, 1.0f);
+        float cLerpY = Clamp((float)y / (float)size.y_, 0.0f, 1.0f);
+
+        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 color = topColor.Lerp(bottomColor, cLerpY);
+        color.a_ *= element_->GetDerivedOpacity();
+        return color.ToUInt();
+    }
+    else
+    {
+        Color color = element_->GetColor(C_TOPLEFT);
+        color.a_ *= element_->GetDerivedOpacity();
+        return color.ToUInt();
+    }
+}
+
+void SystemUIBatch::AddOrMerge(const SystemUIBatch& batch, PODVector<SystemUIBatch>& batches)
+{
+    if (batch.vertexEnd_ == batch.vertexStart_)
+        return;
+
+    if (!batches.Empty() && batches.Back().Merge(batch))
+        return;
+
+    batches.Push(batch);
+}
+
+}
+
+}

+ 99 - 0
Source/Atomic/UI/SystemUI/SystemUIBatch.h

@@ -0,0 +1,99 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "../../Math/Color.h"
+#include "../../Math/Rect.h"
+#include "../../Graphics/GraphicsDefs.h"
+
+namespace Atomic
+{
+
+class PixelShader;
+class Graphics;
+class Matrix3x4;
+class Texture;
+
+namespace SystemUI
+{
+
+class UIElement;
+
+static const unsigned UI_VERTEX_SIZE = 6;
+
+/// %UI rendering draw call.
+class ATOMIC_API SystemUIBatch
+{
+public:
+    /// Construct with defaults.
+    SystemUIBatch();
+    /// Construct.
+    SystemUIBatch(UIElement* element, BlendMode blendMode, const IntRect& scissor, Texture* texture, PODVector<float>* vertexData);
+
+    /// Set new color for the batch. Overrides gradient.
+    void SetColor(const Color& color, bool overrideAlpha = false);
+    /// Restore UI element's default color.
+    void SetDefaultColor();
+    /// Add a quad.
+    void AddQuad(int x, int y, int width, int height, int texOffsetX, int texOffsetY, int texWidth = 0, int texHeight = 0);
+    /// Add a quad using a transform matrix.
+    void AddQuad(const Matrix3x4& transform, int x, int y, int width, int height, int texOffsetX, int texOffsetY, int texWidth = 0,
+        int texHeight = 0);
+    /// Add a quad with tiled texture.
+    void AddQuad(int x, int y, int width, int height, int texOffsetX, int texOffsetY, int texWidth, int texHeight, bool tiled);
+    /// Merge with another batch.
+    bool Merge(const SystemUIBatch& batch);
+    /// Return an interpolated color for the UI element.
+    unsigned GetInterpolatedColor(int x, int y);
+
+    /// Add or merge a batch.
+    static void AddOrMerge(const SystemUIBatch& batch, PODVector<SystemUIBatch>& batches);
+
+    /// Element this batch represents.
+    UIElement* element_;
+    /// Blending mode.
+    BlendMode blendMode_;
+    /// Scissor rectangle.
+    IntRect scissor_;
+    /// Texture.
+    Texture* texture_;
+    /// Inverse texture size.
+    Vector2 invTextureSize_;
+    /// Current color. By default calculated from the element.
+    unsigned color_;
+    /// Vertex data.
+    PODVector<float>* vertexData_;
+    /// Vertex data start index.
+    unsigned vertexStart_;
+    /// Vertex data end index.
+    unsigned vertexEnd_;
+    /// Gradient flag.
+    bool useGradient_;
+
+    /// Position adjustment vector for pixel-perfect rendering. Initialized by UI.
+    static Vector3 posAdjust;
+};
+
+}
+
+}

+ 389 - 0
Source/Atomic/UI/SystemUI/SystemUIEvents.h

@@ -0,0 +1,389 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "../../Core/Object.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+/// Mouse click in the UI.
+EVENT(E_UIMOUSECLICK, UIMouseClick)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_X, X);                          // int
+    PARAM(P_Y, Y);                          // int
+    PARAM(P_BUTTON, Button);                // int
+    PARAM(P_BUTTONS, Buttons);              // int
+    PARAM(P_QUALIFIERS, Qualifiers);        // int
+}
+
+/// Mouse click end in the UI.
+EVENT(E_UIMOUSECLICKEND, UIMouseClickEnd)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_BEGINELEMENT, BeginElement);    // UIElement pointer
+    PARAM(P_X, X);                          // int
+    PARAM(P_Y, Y);                          // int
+    PARAM(P_BUTTON, Button);                // int
+    PARAM(P_BUTTONS, Buttons);              // int
+    PARAM(P_QUALIFIERS, Qualifiers);        // int
+}
+
+/// Mouse double click in the UI.
+EVENT(E_UIMOUSEDOUBLECLICK, UIMouseDoubleClick)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_X, X);                          // int
+    PARAM(P_Y, Y);                          // int
+    PARAM(P_BUTTON, Button);                // int
+    PARAM(P_BUTTONS, Buttons);              // int
+    PARAM(P_QUALIFIERS, Qualifiers);        // int
+}
+
+/// Drag and drop test.
+EVENT(E_DRAGDROPTEST, DragDropTest)
+{
+    PARAM(P_SOURCE, Source);                // UIElement pointer
+    PARAM(P_TARGET, Target);                // UIElement pointer
+    PARAM(P_ACCEPT, Accept);                // bool
+};
+
+/// Drag and drop finish.
+EVENT(E_DRAGDROPFINISH, DragDropFinish)
+{
+    PARAM(P_SOURCE, Source);                // UIElement pointer
+    PARAM(P_TARGET, Target);                // UIElement pointer
+    PARAM(P_ACCEPT, Accept);                // bool
+};
+
+/// Focus element changed.
+EVENT(E_FOCUSCHANGED, FocusChanged)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_CLICKEDELEMENT, ClickedElement); // UIElement pointer
+}
+
+/// UI element name changed.
+EVENT(E_NAMECHANGED, NameChanged)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+}
+
+/// UI element resized.
+EVENT(E_RESIZED, Resized)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_WIDTH, Width);                  // int
+    PARAM(P_HEIGHT, Height);                // int
+}
+
+/// UI element positioned.
+EVENT(E_POSITIONED, Positioned)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_X, X);                          // int
+    PARAM(P_Y, Y);                          // int
+}
+
+/// UI element visibility changed.
+EVENT(E_VISIBLECHANGED, VisibleChanged)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_VISIBLE, Visible);              // bool
+}
+
+/// UI element focused.
+EVENT(E_FOCUSED, Focused)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_BYKEY, ByKey);                  // bool
+}
+
+/// UI element defocused.
+EVENT(E_DEFOCUSED, Defocused)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+}
+
+/// UI element layout updated.
+EVENT(E_LAYOUTUPDATED, LayoutUpdated)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+}
+
+/// UI button pressed.
+EVENT(E_PRESSED, Pressed)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+}
+
+/// UI button was pressed, then released.
+EVENT(E_RELEASED, Released)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+}
+
+/// UI checkbox toggled.
+EVENT(E_TOGGLED, Toggled)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_STATE, State);                  // bool
+}
+
+/// UI slider value changed
+EVENT(E_SLIDERCHANGED, SliderChanged)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_VALUE, Value);                  // float
+}
+
+/// UI slider being paged.
+EVENT(E_SLIDERPAGED, SliderPaged)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_OFFSET, Offset);                // int
+    PARAM(P_PRESSED, Pressed);              // bool
+}
+
+/// UI scrollbar value changed.
+EVENT(E_SCROLLBARCHANGED, ScrollBarChanged)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_VALUE, Value);                  // float
+}
+
+/// UI scrollview position changed.
+EVENT(E_VIEWCHANGED, ViewChanged)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_X, X);                          // int
+    PARAM(P_Y, Y);                          // int
+}
+
+/// UI modal changed (currently only Window has modal flag).
+EVENT(E_MODALCHANGED, ModalChanged)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_MODAL, Modal);                  // bool
+}
+
+/// Text entry into a LineEdit. The char can be modified in the event data.
+EVENT(E_TEXTENTRY, CharEntry)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_TEXT, Text);                    // String
+    PARAM(P_BUTTONS, Buttons);              // int
+    PARAM(P_QUALIFIERS, Qualifiers);        // int
+}
+
+/// Editable text changed
+EVENT(E_TEXTCHANGED, TextChanged)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_TEXT, Text);                    // String
+}
+
+/// Text editing finished (enter pressed on a LineEdit)
+EVENT(E_TEXTFINISHED, TextFinished)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_TEXT, Text);                    // String
+    PARAM(P_VALUE, Value);                 // Float
+}
+
+/// Menu selected.
+EVENT(E_MENUSELECTED, MenuSelected)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+}
+
+/// Listview or DropDownList item selected.
+EVENT(E_ITEMSELECTED, ItemSelected)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_SELECTION, Selection);          // int
+}
+
+/// Listview item deselected.
+EVENT(E_ITEMDESELECTED, ItemDeselected)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_SELECTION, Selection);          // int
+}
+
+/// Listview selection change finished.
+EVENT(E_SELECTIONCHANGED, SelectionChanged)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+}
+
+/// Listview item clicked. If this is a left-click, also ItemSelected event will be sent. If this is a right-click, only this event is sent.
+EVENT(E_ITEMCLICKED, ItemClicked)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_ITEM, Item);                    // UIElement pointer
+    PARAM(P_SELECTION, Selection);          // int
+    PARAM(P_BUTTON, Button);                // int
+    PARAM(P_BUTTONS, Buttons);              // int
+    PARAM(P_QUALIFIERS, Qualifiers);        // int
+}
+
+/// Listview item double clicked.
+EVENT(E_ITEMDOUBLECLICKED, ItemDoubleClicked)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_ITEM, Item);                    // UIElement pointer
+    PARAM(P_SELECTION, Selection);          // int
+    PARAM(P_BUTTON, Button);                // int
+    PARAM(P_BUTTONS, Buttons);              // int
+    PARAM(P_QUALIFIERS, Qualifiers);        // int
+}
+
+/// LineEdit or ListView unhandled key pressed.
+EVENT(E_UNHANDLEDKEY, UnhandledKey)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_KEY, Key);                      // int
+    PARAM(P_BUTTONS, Buttons);              // int
+    PARAM(P_QUALIFIERS, Qualifiers);        // int
+}
+
+/// Fileselector choice.
+EVENT(E_FILESELECTED, FileSelected)
+{
+    PARAM(P_FILENAME, FileName);            // String
+    PARAM(P_FILTER, Filter);                // String
+    PARAM(P_OK, Ok);                        // bool
+}
+
+/// MessageBox acknowlegement.
+EVENT(E_MESSAGEACK, MessageACK)
+{
+    PARAM(P_OK, Ok);                        // bool
+}
+
+/// A child element has been added to an element. Sent by the UI root element, or element-event-sender if set.
+EVENT(E_ELEMENTADDED, ElementAdded)
+{
+    PARAM(P_ROOT, Root);                    // UIElement pointer
+    PARAM(P_PARENT, Parent);                // UIElement pointer
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+}
+
+/// A child element is about to be removed from an element. Sent by the UI root element, or element-event-sender if set.
+EVENT(E_ELEMENTREMOVED, ElementRemoved)
+{
+    PARAM(P_ROOT, Root);                    // UIElement pointer
+    PARAM(P_PARENT, Parent);                // UIElement pointer
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+}
+
+/// Hovering on an UI element has started
+EVENT(E_HOVERBEGIN, HoverBegin)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_X, X);                          // int
+    PARAM(P_Y, Y);                          // int
+    PARAM(P_ELEMENTX, ElementX);            // int
+    PARAM(P_ELEMENTY, ElementY);            // int
+}
+
+/// Hovering on an UI element has ended
+EVENT(E_HOVEREND, HoverEnd)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+}
+
+/// Drag behavior of a UI Element has started
+EVENT(E_DRAGBEGIN, DragBegin)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_X, X);                          // int
+    PARAM(P_Y, Y);                          // int
+    PARAM(P_ELEMENTX, ElementX);            // int
+    PARAM(P_ELEMENTY, ElementY);            // int
+    PARAM(P_BUTTONS, Buttons);              // int
+    PARAM(P_NUMBUTTONS, NumButtons);        // int
+}
+
+/// Drag behavior of a UI Element when the input device has moved
+EVENT(E_DRAGMOVE, DragMove)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_X, X);                          // int
+    PARAM(P_Y, Y);                          // int
+    PARAM(P_DX, DX);                        // int
+    PARAM(P_DY, DY);                        // int
+    PARAM(P_ELEMENTX, ElementX);            // int
+    PARAM(P_ELEMENTY, ElementY);            // int
+    PARAM(P_BUTTONS, Buttons);              // int
+    PARAM(P_NUMBUTTONS, NumButtons);        // int
+}
+
+/// Drag behavior of a UI Element has finished
+EVENT(E_DRAGEND, DragEnd)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_X, X);                          // int
+    PARAM(P_Y, Y);                          // int
+    PARAM(P_ELEMENTX, ElementX);            // int
+    PARAM(P_ELEMENTY, ElementY);            // int
+    PARAM(P_BUTTONS, Buttons);              // int
+    PARAM(P_NUMBUTTONS, NumButtons);        // int
+}
+
+/// Drag of a UI Element was canceled by pressing ESC
+EVENT(E_DRAGCANCEL, DragCancel)
+{
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_X, X);                          // int
+    PARAM(P_Y, Y);                          // int
+    PARAM(P_ELEMENTX, ElementX);            // int
+    PARAM(P_ELEMENTY, ElementY);            // int
+    PARAM(P_BUTTONS, Buttons);              // int
+    PARAM(P_NUMBUTTONS, NumButtons);        // int
+}
+
+/// A file was drag-dropped into the application window. Includes also coordinates and UI element if applicable
+EVENT(E_UIDROPFILE, UIDropFile)
+{
+    PARAM(P_FILENAME, FileName);            // String
+    PARAM(P_ELEMENT, Element);              // UIElement pointer
+    PARAM(P_X, X);                          // int
+    PARAM(P_Y, Y);                          // int
+    PARAM(P_ELEMENTX, ElementX);            // int (only if element is non-null)
+    PARAM(P_ELEMENTY, ElementY);            // int (only if element is non-null)
+}
+
+EVENT(E_CONSOLECLOSED, ConsoleClosed)
+{
+
+}
+
+}
+
+}

+ 770 - 0
Source/Atomic/UI/SystemUI/Text.cpp

@@ -0,0 +1,770 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Core/Profiler.h"
+#include "../../Graphics/Texture2D.h"
+#include "../../IO/Log.h"
+#include "../../Resource/ResourceCache.h"
+#include "Font.h"
+#include "FontFace.h"
+#include "Text.h"
+//#include "../../Resource/Localization.h"
+#include "../../Resource/ResourceEvents.h"
+
+#include "../../DebugNew.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+const char* textEffects[] =
+{
+    "None",
+    "Shadow",
+    "Stroke",
+    0
+};
+
+static const float MIN_ROW_SPACING = 0.5f;
+
+extern const char* horizontalAlignments[];
+extern const char* UI_CATEGORY;
+
+Text::Text(Context* context) :
+    UIElement(context),
+    usedInText3D_(false),
+    fontSize_(DEFAULT_FONT_SIZE),
+    textAlignment_(HA_LEFT),
+    rowSpacing_(1.0f),
+    wordWrap_(false),
+    autoLocalizable_(false),
+    charLocationsDirty_(true),
+    selectionStart_(0),
+    selectionLength_(0),
+    selectionColor_(Color::TRANSPARENT),
+    hoverColor_(Color::TRANSPARENT),
+    textEffect_(TE_NONE),
+    effectColor_(Color::BLACK),
+    effectDepthBias_(0.0f),
+    rowHeight_(0)
+{
+    // By default Text does not derive opacity from parent elements
+    useDerivedOpacity_ = false;
+}
+
+Text::~Text()
+{
+}
+
+void Text::RegisterObject(Context* context)
+{
+    context->RegisterFactory<Text>(UI_CATEGORY);
+
+    COPY_BASE_ATTRIBUTES(UIElement);
+    UPDATE_ATTRIBUTE_DEFAULT_VALUE("Use Derived Opacity", false);
+    MIXED_ACCESSOR_ATTRIBUTE("Font", GetFontAttr, SetFontAttr, ResourceRef, ResourceRef(Font::GetTypeStatic()), AM_FILE);
+    ATTRIBUTE("Font Size", int, fontSize_, DEFAULT_FONT_SIZE, AM_FILE);
+    ATTRIBUTE("Text", String, text_, String::EMPTY, AM_FILE);
+    ENUM_ATTRIBUTE("Text Alignment", textAlignment_, horizontalAlignments, HA_LEFT, AM_FILE);
+    ATTRIBUTE("Row Spacing", float, rowSpacing_, 1.0f, AM_FILE);
+    ATTRIBUTE("Word Wrap", bool, wordWrap_, false, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Auto Localizable", GetAutoLocalizable, SetAutoLocalizable, bool, false, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Selection Color", GetSelectionColor, SetSelectionColor, Color, Color::TRANSPARENT, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Hover Color", GetHoverColor, SetHoverColor, Color, Color::TRANSPARENT, AM_FILE);
+    ENUM_ATTRIBUTE("Text Effect", textEffect_, textEffects, TE_NONE, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Effect Color", GetEffectColor, SetEffectColor, Color, Color::BLACK, AM_FILE);
+
+    // Change the default value for UseDerivedOpacity
+    context->GetAttribute<Text>("Use Derived Opacity")->defaultValue_ = false;
+}
+
+void Text::ApplyAttributes()
+{
+    UIElement::ApplyAttributes();
+
+    DecodeToUnicode();
+
+    fontSize_ = Max(fontSize_, 1);
+    ValidateSelection();
+    UpdateText();
+}
+
+void Text::GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor)
+{
+    FontFace* face = font_ ? font_->GetFace(fontSize_) : (FontFace*)0;
+    if (!face)
+    {
+        hovering_ = false;
+        return;
+    }
+
+    // If face has changed or char locations are not valid anymore, update before rendering
+    if (charLocationsDirty_ || !fontFace_ || face != fontFace_)
+        UpdateCharLocations();
+    // If face uses mutable glyphs mechanism, reacquire glyphs before rendering to make sure they are in the texture
+    else if (face->HasMutableGlyphs())
+    {
+        for (unsigned i = 0; i < printText_.Size(); ++i)
+            face->GetGlyph(printText_[i]);
+    }
+
+    // Hovering and/or whole selection batch
+    if ((hovering_ && hoverColor_.a_ > 0.0) || (selected_ && selectionColor_.a_ > 0.0f))
+    {
+        bool both = hovering_ && selected_ && hoverColor_.a_ > 0.0 && selectionColor_.a_ > 0.0f;
+        SystemUIBatch batch(this, BLEND_ALPHA, currentScissor, 0, &vertexData);
+        batch.SetColor(both ? selectionColor_.Lerp(hoverColor_, 0.5f) :
+            (selected_ && selectionColor_.a_ > 0.0f ? selectionColor_ : hoverColor_));
+        batch.AddQuad(0, 0, GetWidth(), GetHeight(), 0, 0);
+        SystemUIBatch::AddOrMerge(batch, batches);
+    }
+
+    // Partial selection batch
+    if (!selected_ && selectionLength_ && charLocations_.Size() >= selectionStart_ + selectionLength_ && selectionColor_.a_ > 0.0f)
+    {
+        SystemUIBatch batch(this, BLEND_ALPHA, currentScissor, 0, &vertexData);
+        batch.SetColor(selectionColor_);
+
+        IntVector2 currentStart = charLocations_[selectionStart_].position_;
+        IntVector2 currentEnd = currentStart;
+        for (unsigned i = selectionStart_; i < selectionStart_ + selectionLength_; ++i)
+        {
+            // Check if row changes, and start a new quad in that case
+            if (charLocations_[i].size_ != IntVector2::ZERO)
+            {
+                if (charLocations_[i].position_.y_ != currentStart.y_)
+                {
+                    batch.AddQuad(currentStart.x_, currentStart.y_, currentEnd.x_ - currentStart.x_,
+                        currentEnd.y_ - currentStart.y_, 0, 0);
+                    currentStart = charLocations_[i].position_;
+                    currentEnd = currentStart + charLocations_[i].size_;
+                }
+                else
+                {
+                    currentEnd.x_ += charLocations_[i].size_.x_;
+                    currentEnd.y_ = Max(currentStart.y_ + charLocations_[i].size_.y_, currentEnd.y_);
+                }
+            }
+        }
+        if (currentEnd != currentStart)
+        {
+            batch.AddQuad(currentStart.x_, currentStart.y_, currentEnd.x_ - currentStart.x_, currentEnd.y_ - currentStart.y_, 0, 0);
+        }
+
+        SystemUIBatch::AddOrMerge(batch, batches);
+    }
+
+    // Text batch
+    TextEffect textEffect = font_->IsSDFFont() ? TE_NONE : textEffect_;
+    const Vector<SharedPtr<Texture2D> >& textures = face->GetTextures();
+    for (unsigned n = 0; n < textures.Size() && n < pageGlyphLocations_.Size(); ++n)
+    {
+        // One batch per texture/page
+        SystemUIBatch pageBatch(this, BLEND_ALPHA, currentScissor, textures[n], &vertexData);
+
+        const PODVector<GlyphLocation>& pageGlyphLocation = pageGlyphLocations_[n];
+
+        switch (textEffect)
+        {
+        case TE_NONE:
+            ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
+            break;
+
+        case TE_SHADOW:
+            ConstructBatch(pageBatch, pageGlyphLocation, 1, 1, &effectColor_, effectDepthBias_);
+            ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
+            break;
+
+        case TE_STROKE:
+            ConstructBatch(pageBatch, pageGlyphLocation, -1, -1, &effectColor_, effectDepthBias_);
+            ConstructBatch(pageBatch, pageGlyphLocation, 0, -1, &effectColor_, effectDepthBias_);
+            ConstructBatch(pageBatch, pageGlyphLocation, 1, -1, &effectColor_, effectDepthBias_);
+            ConstructBatch(pageBatch, pageGlyphLocation, -1, 0, &effectColor_, effectDepthBias_);
+            ConstructBatch(pageBatch, pageGlyphLocation, 1, 0, &effectColor_, effectDepthBias_);
+            ConstructBatch(pageBatch, pageGlyphLocation, -1, 1, &effectColor_, effectDepthBias_);
+            ConstructBatch(pageBatch, pageGlyphLocation, 0, 1, &effectColor_, effectDepthBias_);
+            ConstructBatch(pageBatch, pageGlyphLocation, 1, 1, &effectColor_, effectDepthBias_);
+            ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
+            break;
+        }
+
+        SystemUIBatch::AddOrMerge(pageBatch, batches);
+    }
+
+    // Reset hovering for next frame
+    hovering_ = false;
+}
+
+void Text::OnResize()
+{
+    if (wordWrap_)
+        UpdateText(true);
+    else
+        charLocationsDirty_ = true;
+}
+
+void Text::OnIndentSet()
+{
+    charLocationsDirty_ = true;
+}
+
+bool Text::SetFont(const String& fontName, int size)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    return SetFont(cache->GetResource<Font>(fontName), size);
+}
+
+bool Text::SetFont(Font* font, int size)
+{
+    if (!font)
+    {
+        LOGERROR("Null font for Text");
+        return false;
+    }
+
+    if (font != font_ || size != fontSize_)
+    {
+        font_ = font;
+        fontSize_ = Max(size, 1);
+        UpdateText();
+    }
+
+    return true;
+}
+
+void Text::DecodeToUnicode()
+{
+    unicodeText_.Clear();
+    for (unsigned i = 0; i < text_.Length();)
+        unicodeText_.Push(text_.NextUTF8Char(i));
+}
+
+void Text::SetText(const String& text)
+{
+    /*
+    if (autoLocalizable_)
+    {
+        stringId_ = text;
+        Localization* l10n = GetSubsystem<Localization>();
+        text_ = l10n->Get(stringId_);
+    }
+    else
+    {*/
+        text_ = text;
+    /*
+    }
+    */
+
+    DecodeToUnicode();
+    ValidateSelection();
+    UpdateText();
+}
+
+void Text::SetTextAlignment(HorizontalAlignment align)
+{
+    if (align != textAlignment_)
+    {
+        textAlignment_ = align;
+        charLocationsDirty_ = true;
+    }
+}
+
+void Text::SetRowSpacing(float spacing)
+{
+    if (spacing != rowSpacing_)
+    {
+        rowSpacing_ = Max(spacing, MIN_ROW_SPACING);
+        UpdateText();
+    }
+}
+
+void Text::SetWordwrap(bool enable)
+{
+    if (enable != wordWrap_)
+    {
+        wordWrap_ = enable;
+        UpdateText();
+    }
+}
+
+void Text::SetAutoLocalizable(bool enable)
+{
+    /*
+    if (enable != autoLocalizable_)
+    {
+        autoLocalizable_ = enable;
+        if (enable)
+        {
+            stringId_ = text_;
+            Localization* l10n = GetSubsystem<Localization>();
+            text_ = l10n->Get(stringId_);
+            SubscribeToEvent(E_CHANGELANGUAGE, HANDLER(Text, HandleChangeLanguage));
+        }
+        else
+        {
+            text_ = stringId_;
+            stringId_ = "";
+            UnsubscribeFromEvent(E_CHANGELANGUAGE);
+        }
+        DecodeToUnicode();
+        ValidateSelection();
+        UpdateText();
+    }
+    */
+}
+
+void Text::HandleChangeLanguage(StringHash eventType, VariantMap& eventData)
+{
+    /*
+    Localization* l10n = GetSubsystem<Localization>();
+    text_ = l10n->Get(stringId_);
+    DecodeToUnicode();
+    ValidateSelection();
+    UpdateText();
+    */
+}
+
+void Text::SetSelection(unsigned start, unsigned length)
+{
+    selectionStart_ = start;
+    selectionLength_ = length;
+    ValidateSelection();
+}
+
+void Text::ClearSelection()
+{
+    selectionStart_ = 0;
+    selectionLength_ = 0;
+}
+
+void Text::SetSelectionColor(const Color& color)
+{
+    selectionColor_ = color;
+}
+
+void Text::SetHoverColor(const Color& color)
+{
+    hoverColor_ = color;
+}
+
+void Text::SetTextEffect(TextEffect textEffect)
+{
+    textEffect_ = textEffect;
+}
+
+void Text::SetEffectColor(const Color& effectColor)
+{
+    effectColor_ = effectColor;
+}
+
+void Text::SetUsedInText3D(bool usedInText3D)
+{
+    usedInText3D_ = usedInText3D;
+}
+
+void Text::SetEffectDepthBias(float bias)
+{
+    effectDepthBias_ = bias;
+}
+
+int Text::GetRowWidth(unsigned index) const
+{
+    return index < rowWidths_.Size() ? rowWidths_[index] : 0;
+}
+
+IntVector2 Text::GetCharPosition(unsigned index)
+{
+    if (charLocationsDirty_)
+        UpdateCharLocations();
+    if (charLocations_.Empty())
+        return IntVector2::ZERO;
+    // For convenience, return the position of the text ending if index exceeded
+    if (index > charLocations_.Size() - 1)
+        index = charLocations_.Size() - 1;
+    return charLocations_[index].position_;
+}
+
+IntVector2 Text::GetCharSize(unsigned index)
+{
+    if (charLocationsDirty_)
+        UpdateCharLocations();
+    if (charLocations_.Size() < 2)
+        return IntVector2::ZERO;
+    // For convenience, return the size of the last char if index exceeded (last size entry is zero)
+    if (index > charLocations_.Size() - 2)
+        index = charLocations_.Size() - 2;
+    return charLocations_[index].size_;
+}
+
+void Text::SetFontAttr(const ResourceRef& value)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    font_ = cache->GetResource<Font>(value.name_);
+}
+
+ResourceRef Text::GetFontAttr() const
+{
+    return GetResourceRef(font_, Font::GetTypeStatic());
+}
+
+bool Text::FilterImplicitAttributes(XMLElement& dest) const
+{
+    if (!UIElement::FilterImplicitAttributes(dest))
+        return false;
+
+    if (!IsFixedWidth())
+    {
+        if (!RemoveChildXML(dest, "Size"))
+            return false;
+        if (!RemoveChildXML(dest, "Min Size"))
+            return false;
+        if (!RemoveChildXML(dest, "Max Size"))
+            return false;
+    }
+
+    return true;
+}
+
+void Text::UpdateText(bool onResize)
+{
+    rowWidths_.Clear();
+    printText_.Clear();
+
+    if (font_)
+    {
+        FontFace* face = font_->GetFace(fontSize_);
+        if (!face)
+            return;
+
+        rowHeight_ = face->GetRowHeight();
+
+        int width = 0;
+        int height = 0;
+        int rowWidth = 0;
+        int rowHeight = (int)(rowSpacing_ * rowHeight_);
+
+        // First see if the text must be split up
+        if (!wordWrap_)
+        {
+            printText_ = unicodeText_;
+            printToText_.Resize(printText_.Size());
+            for (unsigned i = 0; i < printText_.Size(); ++i)
+                printToText_[i] = i;
+        }
+        else
+        {
+            int maxWidth = GetWidth();
+            unsigned nextBreak = 0;
+            unsigned lineStart = 0;
+            printToText_.Clear();
+
+            for (unsigned i = 0; i < unicodeText_.Size(); ++i)
+            {
+                unsigned j;
+                unsigned c = unicodeText_[i];
+
+                if (c != '\n')
+                {
+                    bool ok = true;
+
+                    if (nextBreak <= i)
+                    {
+                        int futureRowWidth = rowWidth;
+                        for (j = i; j < unicodeText_.Size(); ++j)
+                        {
+                            unsigned d = unicodeText_[j];
+                            if (d == ' ' || d == '\n')
+                            {
+                                nextBreak = j;
+                                break;
+                            }
+                            const FontGlyph* glyph = face->GetGlyph(d);
+                            if (glyph)
+                            {
+                                futureRowWidth += glyph->advanceX_;
+                                if (j < unicodeText_.Size() - 1)
+                                    futureRowWidth += face->GetKerning(d, unicodeText_[j + 1]);
+                            }
+                            if (d == '-' && futureRowWidth <= maxWidth)
+                            {
+                                nextBreak = j + 1;
+                                break;
+                            }
+                            if (futureRowWidth > maxWidth)
+                            {
+                                ok = false;
+                                break;
+                            }
+                        }
+                    }
+
+                    if (!ok)
+                    {
+                        // If did not find any breaks on the line, copy until j, or at least 1 char, to prevent infinite loop
+                        if (nextBreak == lineStart)
+                        {
+                            while (i < j)
+                            {
+                                printText_.Push(unicodeText_[i]);
+                                printToText_.Push(i);
+                                ++i;
+                            }
+                        }
+                        // Eliminate spaces that have been copied before the forced break
+                        while (printText_.Size() && printText_.Back() == ' ')
+                        {
+                            printText_.Pop();
+                            printToText_.Pop();
+                        }
+                        printText_.Push('\n');
+                        printToText_.Push((unsigned)Min((int)i, (int)unicodeText_.Size() - 1));
+                        rowWidth = 0;
+                        nextBreak = lineStart = i;
+                    }
+
+                    if (i < unicodeText_.Size())
+                    {
+                        // When copying a space, position is allowed to be over row width
+                        c = unicodeText_[i];
+                        const FontGlyph* glyph = face->GetGlyph(c);
+                        if (glyph)
+                        {
+                            rowWidth += glyph->advanceX_;
+                            if (i < unicodeText_.Size() - 1)
+                                rowWidth += face->GetKerning(c, unicodeText_[i + 1]);
+                        }
+                        if (rowWidth <= maxWidth)
+                        {
+                            printText_.Push(c);
+                            printToText_.Push(i);
+                        }
+                    }
+                }
+                else
+                {
+                    printText_.Push('\n');
+                    printToText_.Push((unsigned)Min((int)i, (int)unicodeText_.Size() - 1));
+                    rowWidth = 0;
+                    nextBreak = lineStart = i;
+                }
+            }
+        }
+
+        rowWidth = 0;
+
+        for (unsigned i = 0; i < printText_.Size(); ++i)
+        {
+            unsigned c = printText_[i];
+
+            if (c != '\n')
+            {
+                const FontGlyph* glyph = face->GetGlyph(c);
+                if (glyph)
+                {
+                    rowWidth += glyph->advanceX_;
+                    if (i < printText_.Size() - 1)
+                        rowWidth += face->GetKerning(c, printText_[i + 1]);
+                }
+            }
+            else
+            {
+                width = Max(width, rowWidth);
+                height += rowHeight;
+                rowWidths_.Push(rowWidth);
+                rowWidth = 0;
+            }
+        }
+
+        if (rowWidth)
+        {
+            width = Max(width, rowWidth);
+            height += rowHeight;
+            rowWidths_.Push(rowWidth);
+        }
+
+        // Set at least one row height even if text is empty
+        if (!height)
+            height = rowHeight;
+
+        // Set minimum and current size according to the text size, but respect fixed width if set
+        if (!IsFixedWidth())
+        {
+            SetMinWidth(wordWrap_ ? 0 : width);
+            SetWidth(width);
+        }
+        SetFixedHeight(height);
+
+        charLocationsDirty_ = true;
+    }
+    else
+    {
+        // No font, nothing to render
+        pageGlyphLocations_.Clear();
+    }
+
+    // If wordwrap is on, parent may need layout update to correct for overshoot in size. However, do not do this when the
+    // update is a response to resize, as that could cause infinite recursion
+    if (wordWrap_ && !onResize)
+    {
+        UIElement* parent = GetParent();
+        if (parent && parent->GetLayoutMode() != LM_FREE)
+            parent->UpdateLayout();
+    }
+}
+
+void Text::UpdateCharLocations()
+{
+    // Remember the font face to see if it's still valid when it's time to render
+    FontFace* face = font_ ? font_->GetFace(fontSize_) : (FontFace*)0;
+    if (!face)
+        return;
+    fontFace_ = face;
+
+    int rowHeight = (int)(rowSpacing_ * rowHeight_);
+
+    // Store position & size of each character, and locations per texture page
+    unsigned numChars = unicodeText_.Size();
+    charLocations_.Resize(numChars + 1);
+    pageGlyphLocations_.Resize(face->GetTextures().Size());
+    for (unsigned i = 0; i < pageGlyphLocations_.Size(); ++i)
+        pageGlyphLocations_[i].Clear();
+
+    IntVector2 offset = font_->GetTotalGlyphOffset(fontSize_);
+
+    unsigned rowIndex = 0;
+    unsigned lastFilled = 0;
+    int x = GetRowStartPosition(rowIndex) + offset.x_;
+    int y = offset.y_;
+
+    for (unsigned i = 0; i < printText_.Size(); ++i)
+    {
+        CharLocation loc;
+        loc.position_ = IntVector2(x, y);
+
+        unsigned c = printText_[i];
+        if (c != '\n')
+        {
+            const FontGlyph* glyph = face->GetGlyph(c);
+            loc.size_ = IntVector2(glyph ? glyph->advanceX_ : 0, rowHeight_);
+            if (glyph)
+            {
+                // Store glyph's location for rendering. Verify that glyph page is valid
+                if (glyph->page_ < pageGlyphLocations_.Size())
+                    pageGlyphLocations_[glyph->page_].Push(GlyphLocation(x, y, glyph));
+                x += glyph->advanceX_;
+                if (i < printText_.Size() - 1)
+                    x += face->GetKerning(c, printText_[i + 1]);
+            }
+        }
+        else
+        {
+            loc.size_ = IntVector2::ZERO;
+            x = GetRowStartPosition(++rowIndex);
+            y += rowHeight;
+        }
+
+        // Fill gaps in case characters were skipped from printing
+        for (unsigned j = lastFilled; j <= printToText_[i]; ++j)
+            charLocations_[j] = loc;
+        lastFilled = printToText_[i] + 1;
+    }
+    // Store the ending position
+    charLocations_[numChars].position_ = IntVector2(x, y);
+    charLocations_[numChars].size_ = IntVector2::ZERO;
+
+    charLocationsDirty_ = false;
+}
+
+void Text::ValidateSelection()
+{
+    unsigned textLength = unicodeText_.Size();
+
+    if (textLength)
+    {
+        if (selectionStart_ >= textLength)
+            selectionStart_ = textLength - 1;
+        if (selectionStart_ + selectionLength_ > textLength)
+            selectionLength_ = textLength - selectionStart_;
+    }
+    else
+    {
+        selectionStart_ = 0;
+        selectionLength_ = 0;
+    }
+}
+
+int Text::GetRowStartPosition(unsigned rowIndex) const
+{
+    int rowWidth = 0;
+
+    if (rowIndex < rowWidths_.Size())
+        rowWidth = rowWidths_[rowIndex];
+
+    int ret = GetIndentWidth();
+
+    switch (textAlignment_)
+    {
+    case HA_LEFT:
+        break;
+    case HA_CENTER:
+        ret += (GetSize().x_ - rowWidth) / 2;
+        break;
+    case HA_RIGHT:
+        ret += GetSize().x_ - rowWidth;
+        break;
+    }
+
+    return ret;
+}
+
+void Text::ConstructBatch(SystemUIBatch& pageBatch, const PODVector<GlyphLocation>& pageGlyphLocation, int dx, int dy, Color* color,
+    float depthBias)
+{
+    unsigned startDataSize = pageBatch.vertexData_->Size();
+
+    if (!color)
+        pageBatch.SetDefaultColor();
+    else
+        pageBatch.SetColor(*color);
+
+    for (unsigned i = 0; i < pageGlyphLocation.Size(); ++i)
+    {
+        const GlyphLocation& glyphLocation = pageGlyphLocation[i];
+        const FontGlyph& glyph = *glyphLocation.glyph_;
+        pageBatch.AddQuad(dx + glyphLocation.x_ + glyph.offsetX_, dy + glyphLocation.y_ + glyph.offsetY_, glyph.width_,
+            glyph.height_, glyph.x_, glyph.y_);
+    }
+
+    if (depthBias != 0.0f)
+    {
+        unsigned dataSize = pageBatch.vertexData_->Size();
+        for (unsigned i = startDataSize; i < dataSize; i += UI_VERTEX_SIZE)
+            pageBatch.vertexData_->At(i + 2) += depthBias;
+    }
+}
+
+}
+
+}

+ 268 - 0
Source/Atomic/UI/SystemUI/Text.h

@@ -0,0 +1,268 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "UIElement.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+static const int DEFAULT_FONT_SIZE = 12;
+
+class Font;
+class FontFace;
+struct FontGlyph;
+
+/// Text effect.
+enum TextEffect
+{
+    TE_NONE = 0,
+    TE_SHADOW,
+    TE_STROKE
+};
+
+/// Cached character location and size within text. Used for queries related to text editing.
+struct CharLocation
+{
+    /// Position.
+    IntVector2 position_;
+    /// Size.
+    IntVector2 size_;
+};
+
+/// Glyph and its location within the text. Used when preparing text rendering.
+struct GlyphLocation
+{
+    // Construct.
+    GlyphLocation(int x, int y, const FontGlyph* glyph) :
+        x_(x),
+        y_(y),
+        glyph_(glyph)
+    {
+    }
+
+    /// X coordinate.
+    int x_;
+    /// Y coordinate.
+    int y_;
+    /// Glyph.
+    const FontGlyph* glyph_;
+};
+
+/// %Text %UI element.
+class ATOMIC_API Text : public UIElement
+{
+    OBJECT(Text);
+
+    friend class Text3D;
+
+public:
+    /// Construct.
+    Text(Context* context);
+    /// Destruct.
+    virtual ~Text();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Apply attribute changes that can not be applied immediately.
+    virtual void ApplyAttributes();
+    /// Return UI rendering batches.
+    virtual void GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor);
+    /// React to resize.
+    virtual void OnResize();
+    /// React to indent change.
+    virtual void OnIndentSet();
+
+    /// Set font and font size and use signed distance field.
+    bool SetFont(const String& fontName, int size = DEFAULT_FONT_SIZE);
+    /// Set font and font size and use signed distance field.
+    bool SetFont(Font* font, int size = DEFAULT_FONT_SIZE);
+    /// Set text. Text is assumed to be either ASCII or UTF8-encoded.
+    void SetText(const String& text);
+    /// Set row alignment.
+    void SetTextAlignment(HorizontalAlignment align);
+    /// Set row spacing, 1.0 for original font spacing.
+    void SetRowSpacing(float spacing);
+    /// Set wordwrap. In wordwrap mode the text element will respect its current width. Otherwise it resizes itself freely.
+    void SetWordwrap(bool enable);
+    /// The text will be automatically translated. The text value used as string identifier.
+    void SetAutoLocalizable(bool enable);
+    /// Set selection. When length is not provided, select until the text ends.
+    void SetSelection(unsigned start, unsigned length = M_MAX_UNSIGNED);
+    /// Clear selection.
+    void ClearSelection();
+    /// Set selection background color. Color with 0 alpha (default) disables.
+    void SetSelectionColor(const Color& color);
+    /// Set hover background color. Color with 0 alpha (default) disables.
+    void SetHoverColor(const Color& color);
+    /// Set text effect.
+    void SetTextEffect(TextEffect textEffect);
+    /// Set effect color.
+    void SetEffectColor(const Color& effectColor);
+
+    /// Return font.
+    Font* GetFont() const { return font_; }
+
+    /// Return font size.
+    int GetFontSize() const { return fontSize_; }
+
+    /// Return text.
+    const String& GetText() const { return text_; }
+
+    /// Return row alignment.
+    HorizontalAlignment GetTextAlignment() const { return textAlignment_; }
+
+    /// Return row spacing.
+    float GetRowSpacing() const { return rowSpacing_; }
+
+    /// Return wordwrap mode.
+    bool GetWordwrap() const { return wordWrap_; }
+
+    /// Return auto localizable mode.
+    bool GetAutoLocalizable() const { return autoLocalizable_; }
+
+    /// Return selection start.
+    unsigned GetSelectionStart() const { return selectionStart_; }
+
+    /// Return selection length.
+    unsigned GetSelectionLength() const { return selectionLength_; }
+
+    /// Return selection background color.
+    const Color& GetSelectionColor() const { return selectionColor_; }
+
+    /// Return hover background color.
+    const Color& GetHoverColor() const { return hoverColor_; }
+
+    /// Return text effect.
+    TextEffect GetTextEffect() const { return textEffect_; }
+
+    /// Return effect color.
+    const Color& GetEffectColor() const { return effectColor_; }
+
+    /// Return row height.
+    int GetRowHeight() const { return rowHeight_; }
+
+    /// Return number of rows.
+    unsigned GetNumRows() const { return rowWidths_.Size(); }
+
+    /// Return number of characters.
+    unsigned GetNumChars() const { return unicodeText_.Size(); }
+
+    /// Return width of row by index.
+    int GetRowWidth(unsigned index) const;
+    /// Return position of character by index relative to the text element origin.
+    IntVector2 GetCharPosition(unsigned index);
+    /// Return size of character by index.
+    IntVector2 GetCharSize(unsigned index);
+
+    /// Set used in Text3D.
+    void SetUsedInText3D(bool usedInText3D);
+    /// Set text effect Z bias. Zero by default, adjusted only in 3D mode.
+    void SetEffectDepthBias(float bias);
+
+    /// Return effect Z bias.
+    float GetEffectDepthBias() const { return effectDepthBias_; }
+
+    /// Set font attribute.
+    void SetFontAttr(const ResourceRef& value);
+    /// Return font attribute.
+    ResourceRef GetFontAttr() const;
+
+protected:
+    /// Filter implicit attributes in serialization process.
+    virtual bool FilterImplicitAttributes(XMLElement& dest) const;
+    /// Update text when text, font or spacing changed.
+    void UpdateText(bool onResize = false);
+    /// Update cached character locations after text update, or when text alignment or indent has changed.
+    void UpdateCharLocations();
+    /// Validate text selection to be within the text.
+    void ValidateSelection();
+    /// Return row start X position.
+    int GetRowStartPosition(unsigned rowIndex) const;
+    /// Contruct batch.
+    void ConstructBatch
+        (SystemUIBatch& pageBatch, const PODVector<GlyphLocation>& pageGlyphLocation, int dx = 0, int dy = 0, Color* color = 0,
+            float depthBias = 0.0f);
+
+    /// Used in Text3D.
+    bool usedInText3D_;
+    /// Font.
+    SharedPtr<Font> font_;
+    /// Current face.
+    WeakPtr<FontFace> fontFace_;
+    /// Font size.
+    int fontSize_;
+    /// UTF-8 encoded text.
+    String text_;
+    /// Row alignment.
+    HorizontalAlignment textAlignment_;
+    /// Row spacing.
+    float rowSpacing_;
+    /// Wordwrap mode.
+    bool wordWrap_;
+    /// Char positions dirty flag.
+    bool charLocationsDirty_;
+    /// Selection start.
+    unsigned selectionStart_;
+    /// Selection length.
+    unsigned selectionLength_;
+    /// Selection background color.
+    Color selectionColor_;
+    /// Hover background color.
+    Color hoverColor_;
+    /// Text effect.
+    TextEffect textEffect_;
+    /// Effect color.
+    Color effectColor_;
+    /// Text effect Z bias.
+    float effectDepthBias_;
+    /// Row height.
+    int rowHeight_;
+    /// Text as Unicode characters.
+    PODVector<unsigned> unicodeText_;
+    /// Text modified into printed form.
+    PODVector<unsigned> printText_;
+    /// Mapping of printed form back to original char indices.
+    PODVector<unsigned> printToText_;
+    /// Row widths.
+    PODVector<int> rowWidths_;
+    /// Glyph locations per each texture in the font.
+    Vector<PODVector<GlyphLocation> > pageGlyphLocations_;
+    /// Cached locations of each character in the text.
+    PODVector<CharLocation> charLocations_;
+    /// The text will be automatically translated.
+    bool autoLocalizable_;
+    /// Storage string id. Used when enabled autoLocalizable.
+    String stringId_;
+    /// Handle change Language.
+    void HandleChangeLanguage(StringHash eventType, VariantMap& eventData);
+    /// UTF8 to Unicode.
+    void DecodeToUnicode();
+};
+
+}
+
+}

+ 113 - 0
Source/Atomic/UI/SystemUI/ToolTip.cpp

@@ -0,0 +1,113 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "ToolTip.h"
+#include "SystemUI.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+extern const char* UI_CATEGORY;
+
+ToolTip::ToolTip(Context* context) :
+    UIElement(context),
+    delay_(0.0f),
+    parentHovered_(false)
+{
+    SetVisible(false);
+}
+
+ToolTip::~ToolTip()
+{
+}
+
+void ToolTip::RegisterObject(Context* context)
+{
+    context->RegisterFactory<ToolTip>(UI_CATEGORY);
+
+    COPY_BASE_ATTRIBUTES(UIElement);
+    ACCESSOR_ATTRIBUTE("Delay", GetDelay, SetDelay, float, 0.0f, AM_FILE);
+}
+
+void ToolTip::Update(float timeStep)
+{
+    // Track the element we are parented to for hovering. When we display, we move ourself to the root element
+    // to ensure displaying on top
+    UIElement* root = GetRoot();
+    if (!root)
+        return;
+    if (parent_ != root)
+        target_ = parent_;
+
+    // If target is removed while we are displaying, we have no choice but to destroy ourself
+    if (target_.Expired())
+    {
+        Remove();
+        return;
+    }
+
+    if (target_->IsHovering())
+    {
+        float effectiveDelay = delay_ > 0.0f ? delay_ : GetSubsystem<SystemUI>()->GetDefaultToolTipDelay();
+
+        if (!parentHovered_)
+        {
+            parentHovered_ = true;
+            displayAt_.Reset();
+        }
+        else if (displayAt_.GetMSec(false) >= (unsigned)(effectiveDelay * 1000.0f) && parent_ == target_)
+        {
+            originalPosition_ = GetPosition();
+            IntVector2 screenPosition = GetScreenPosition();
+            SetParent(root);
+            SetPosition(screenPosition);
+            SetVisible(true);
+            // BringToFront() is unreliable in this case as it takes into account only input-enabled elements.
+            // Rather just force priority to max
+            SetPriority(M_MAX_INT);
+        }
+    }
+    else
+    {
+        if (IsVisible() && parent_ == root)
+        {
+            SetParent(target_);
+            SetPosition(originalPosition_);
+            SetVisible(false);
+        }
+        parentHovered_ = false;
+        displayAt_.Reset();
+    }
+}
+
+void ToolTip::SetDelay(float delay)
+{
+    delay_ = delay;
+}
+
+}
+
+}

+ 71 - 0
Source/Atomic/UI/SystemUI/ToolTip.h

@@ -0,0 +1,71 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "../../Graphics/GraphicsDefs.h"
+#include "UIElement.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+/// Tooltip %UI element.
+class ATOMIC_API ToolTip : public UIElement
+{
+    OBJECT(ToolTip)
+
+public:
+    /// Construct.
+    ToolTip(Context* context);
+    /// Destruct.
+    virtual ~ToolTip();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Perform UI element update.
+    virtual void Update(float timeStep);
+
+    /// Set the delay in seconds until the tooltip shows once hovering. Set zero to use the default from the UI subsystem.
+    void SetDelay(float delay);
+
+    /// Return the delay in seconds until the tooltip shows once hovering.
+    float GetDelay() const { return delay_; }
+
+private:
+    /// The element that is being tracked for hovering. Normally the parent element.
+    WeakPtr<UIElement> target_;
+    /// Delay from hover start to displaying the tooltip.
+    float delay_;
+    /// Point at which the parent was hovered.
+    bool parentHovered_;
+    /// Point at which the tooltip was set visible.
+    Timer displayAt_;
+    /// Original offset position to the parent.
+    IntVector2 originalPosition_;
+};
+
+}
+
+}

+ 2034 - 0
Source/Atomic/UI/SystemUI/UIElement.cpp

@@ -0,0 +1,2034 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Core/CoreEvents.h"
+#include "../../Container/HashSet.h"
+#include "../../Container/Sort.h"
+#include "../../IO/Log.h"
+#include "../../Resource/ResourceCache.h"
+#include "../../Scene/ObjectAnimation.h"
+#include "Cursor.h"
+#include "SystemUI.h"
+#include "UIElement.h"
+#include "SystemUIEvents.h"
+
+#include "../../DebugNew.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+const char* horizontalAlignments[] =
+{
+    "Left",
+    "Center",
+    "Right",
+    0
+};
+
+const char* verticalAlignments[] =
+{
+    "Top",
+    "Center",
+    "Bottom",
+    0
+};
+
+static const char* focusModes[] =
+{
+    "NotFocusable",
+    "ResetFocus",
+    "Focusable",
+    "FocusableDefocusable",
+    0
+};
+
+static const char* dragDropModes[] =
+{
+    "Disabled",
+    "Source",
+    "Target",
+    "SourceAndTarget",
+    0
+};
+
+static const char* layoutModes[] =
+{
+    "Free",
+    "Horizontal",
+    "Vertical",
+    0
+};
+
+extern const char* UI_CATEGORY;
+
+static bool CompareUIElements(const UIElement* lhs, const UIElement* rhs)
+{
+    return lhs->GetPriority() < rhs->GetPriority();
+}
+
+XPathQuery UIElement::styleXPathQuery_("/elements/element[@type=$typeName]", "typeName:String");
+
+UIElement::UIElement(Context* context) :
+    Animatable(context),
+    parent_(0),
+    clipBorder_(IntRect::ZERO),
+    priority_(0),
+    bringToFront_(false),
+    bringToBack_(true),
+    clipChildren_(false),
+    sortChildren_(true),
+    useDerivedOpacity_(true),
+    editable_(true),
+    selected_(false),
+    visible_(true),
+    hovering_(false),
+    internal_(false),
+    focusMode_(FM_NOTFOCUSABLE),
+    dragDropMode_(DD_DISABLED),
+    layoutMode_(LM_FREE),
+    layoutSpacing_(0),
+    layoutBorder_(IntRect::ZERO),
+    layoutFlexScale_(Vector2::ONE),
+    resizeNestingLevel_(0),
+    layoutNestingLevel_(0),
+    layoutMinSize_(0),
+    layoutMaxSize_(0),
+    indent_(0),
+    indentSpacing_(16),
+    position_(IntVector2::ZERO),
+    positionDirty_(true),
+    dragButtonCombo_(0),
+    dragButtonCount_(0),
+    size_(IntVector2::ZERO),
+    minSize_(IntVector2::ZERO),
+    maxSize_(M_MAX_INT, M_MAX_INT),
+    childOffset_(IntVector2::ZERO),
+    horizontalAlignment_(HA_LEFT),
+    verticalAlignment_(VA_TOP),
+    opacity_(1.0f),
+    opacityDirty_(true),
+    derivedColorDirty_(true),
+    sortOrderDirty_(false),
+    colorGradient_(false),
+    traversalMode_(TM_BREADTH_FIRST),
+    elementEventSender_(false)
+{
+    SetEnabled(false);
+}
+
+UIElement::~UIElement()
+{
+    // If child elements have outside references, detach them
+    for (Vector<SharedPtr<UIElement> >::Iterator i = children_.Begin(); i < children_.End(); ++i)
+    {
+        if (i->Refs() > 1)
+            (*i)->Detach();
+    }
+}
+
+void UIElement::RegisterObject(Context* context)
+{
+    context->RegisterFactory<UIElement>(UI_CATEGORY);
+
+    ACCESSOR_ATTRIBUTE("Name", GetName, SetName, String, String::EMPTY, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Position", GetPosition, SetPosition, IntVector2, IntVector2::ZERO, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Size", GetSize, SetSize, IntVector2, IntVector2::ZERO, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Min Size", GetMinSize, SetMinSize, IntVector2, IntVector2::ZERO, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Max Size", GetMaxSize, SetMaxSize, IntVector2, IntVector2(M_MAX_INT, M_MAX_INT), AM_FILE);
+    ENUM_ACCESSOR_ATTRIBUTE("Horiz Alignment", GetHorizontalAlignment, SetHorizontalAlignment, HorizontalAlignment,
+        horizontalAlignments, HA_LEFT, AM_FILE);
+    ENUM_ACCESSOR_ATTRIBUTE("Vert Alignment", GetVerticalAlignment, SetVerticalAlignment, VerticalAlignment, verticalAlignments,
+        VA_TOP, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Clip Border", GetClipBorder, SetClipBorder, IntRect, IntRect::ZERO, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Priority", GetPriority, SetPriority, int, 0, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Opacity", GetOpacity, SetOpacity, float, 1.0f, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Color", GetColorAttr, SetColor, Color, Color::WHITE, AM_FILE);
+    ATTRIBUTE("Top Left Color", Color, color_[0], Color::WHITE, AM_FILE);
+    ATTRIBUTE("Top Right Color", Color, color_[1], Color::WHITE, AM_FILE);
+    ATTRIBUTE("Bottom Left Color", Color, color_[2], Color::WHITE, AM_FILE);
+    ATTRIBUTE("Bottom Right Color", Color, color_[3], Color::WHITE, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Is Enabled", IsEnabled, SetEnabled, bool, false, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Is Editable", IsEditable, SetEditable, bool, true, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Is Selected", IsSelected, SetSelected, bool, false, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Is Visible", IsVisible, SetVisible, bool, true, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Bring To Front", GetBringToFront, SetBringToFront, bool, false, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Bring To Back", GetBringToBack, SetBringToBack, bool, true, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Clip Children", GetClipChildren, SetClipChildren, bool, false, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Use Derived Opacity", GetUseDerivedOpacity, SetUseDerivedOpacity, bool, true, AM_FILE);
+    ENUM_ACCESSOR_ATTRIBUTE("Focus Mode", GetFocusMode, SetFocusMode, FocusMode, focusModes, FM_NOTFOCUSABLE, AM_FILE);
+    ENUM_ACCESSOR_ATTRIBUTE("Drag And Drop Mode", GetDragDropMode, SetDragDropMode, unsigned, dragDropModes, DD_DISABLED, AM_FILE);
+    ENUM_ACCESSOR_ATTRIBUTE("Layout Mode", GetLayoutMode, SetLayoutMode, LayoutMode, layoutModes, LM_FREE, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Layout Spacing", GetLayoutSpacing, SetLayoutSpacing, int, 0, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Layout Border", GetLayoutBorder, SetLayoutBorder, IntRect, IntRect::ZERO, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Layout Flex Scale", GetLayoutFlexScale, SetLayoutFlexScale, Vector2, Vector2::ONE, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Indent", GetIndent, SetIndent, int, 0, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Indent Spacing", GetIndentSpacing, SetIndentSpacing, int, 16, AM_FILE);
+    ATTRIBUTE("Variables", VariantMap, vars_, Variant::emptyVariantMap, AM_FILE);
+}
+
+void UIElement::ApplyAttributes()
+{
+    colorGradient_ = false;
+    derivedColorDirty_ = true;
+
+    for (unsigned i = 1; i < MAX_UIELEMENT_CORNERS; ++i)
+    {
+        if (color_[i] != color_[0])
+            colorGradient_ = true;
+    }
+}
+
+bool UIElement::LoadXML(const XMLElement& source, bool setInstanceDefault)
+{
+    return LoadXML(source, 0, setInstanceDefault);
+}
+
+bool UIElement::LoadXML(const XMLElement& source, XMLFile* styleFile, bool setInstanceDefault)
+{
+    // Get style override if defined
+    String styleName = source.GetAttribute("style");
+
+    // Apply the style first, if the style file is available
+    if (styleFile)
+    {
+        // If not defined, use type name
+        if (styleName.Empty())
+            styleName = GetTypeName();
+
+        SetStyle(styleName, styleFile);
+    }
+    // The 'style' attribute value in the style file cannot be equals to original's applied style to prevent infinite loop
+    else if (!styleName.Empty() && styleName != appliedStyle_)
+    {
+        // Attempt to use the default style file
+        styleFile = GetDefaultStyle();
+
+        if (styleFile)
+        {
+            // Remember the original applied style
+            String appliedStyle(appliedStyle_);
+            SetStyle(styleName, styleFile);
+            appliedStyle_ = appliedStyle;
+        }
+    }
+
+    // Prevent updates while loading attributes
+    DisableLayoutUpdate();
+
+    // Then load rest of the attributes from the source
+    if (!Animatable::LoadXML(source, setInstanceDefault))
+        return false;
+
+    unsigned nextInternalChild = 0;
+
+    // Load child elements. Internal elements are not to be created as they already exist
+    XMLElement childElem = source.GetChild("element");
+    while (childElem)
+    {
+        bool internalElem = childElem.GetBool("internal");
+        String typeName = childElem.GetAttribute("type");
+        if (typeName.Empty())
+            typeName = "UIElement";
+        unsigned index = childElem.HasAttribute("index") ? childElem.GetUInt("index") : M_MAX_UNSIGNED;
+        UIElement* child = 0;
+
+        if (!internalElem)
+            child = CreateChild(typeName, String::EMPTY, index);
+        else
+        {
+            for (unsigned i = nextInternalChild; i < children_.Size(); ++i)
+            {
+                if (children_[i]->IsInternal() && children_[i]->GetTypeName() == typeName)
+                {
+                    child = children_[i];
+                    nextInternalChild = i + 1;
+                    break;
+                }
+            }
+
+            if (!child)
+                LOGWARNING("Could not find matching internal child element of type " + typeName + " in " + GetTypeName());
+        }
+
+        if (child)
+        {
+            if (!styleFile)
+                styleFile = GetDefaultStyle();
+            if (!child->LoadXML(childElem, styleFile, setInstanceDefault))
+                return false;
+        }
+
+        childElem = childElem.GetNext("element");
+    }
+
+    ApplyAttributes();
+
+    EnableLayoutUpdate();
+    UpdateLayout();
+
+    return true;
+}
+
+bool UIElement::LoadChildXML(const XMLElement& childElem, XMLFile* styleFile, bool setInstanceDefault)
+{
+    bool internalElem = childElem.GetBool("internal");
+    if (internalElem)
+    {
+        LOGERROR("Loading internal child element is not supported");
+        return false;
+    }
+
+    String typeName = childElem.GetAttribute("type");
+    if (typeName.Empty())
+        typeName = "UIElement";
+    unsigned index = childElem.HasAttribute("index") ? childElem.GetUInt("index") : M_MAX_UNSIGNED;
+    UIElement* child = CreateChild(typeName, String::EMPTY, index);
+
+    if (child)
+    {
+        if (!styleFile)
+            styleFile = GetDefaultStyle();
+        if (!child->LoadXML(childElem, styleFile, setInstanceDefault))
+            return false;
+    }
+
+    return true;
+}
+
+bool UIElement::SaveXML(XMLElement& dest) const
+{
+    // Write type
+    if (GetTypeName() != "UIElement")
+    {
+        if (!dest.SetString("type", GetTypeName()))
+            return false;
+    }
+
+    // Write internal flag
+    if (internal_)
+    {
+        if (!dest.SetBool("internal", internal_))
+            return false;
+    }
+
+    // Write style
+    if (!appliedStyle_.Empty() && appliedStyle_ != "UIElement")
+    {
+        if (!dest.SetAttribute("style", appliedStyle_))
+            return false;
+    }
+    else if (internal_)
+    {
+        if (!dest.SetAttribute("style", "none"))
+            return false;
+    }
+
+    // Write attributes
+    if (!Animatable::SaveXML(dest))
+        return false;
+
+    // Write child elements
+    for (unsigned i = 0; i < children_.Size(); ++i)
+    {
+        UIElement* element = children_[i];
+        if (element->IsTemporary())
+            continue;
+
+        XMLElement childElem = dest.CreateChild("element");
+        if (!element->SaveXML(childElem))
+            return false;
+    }
+
+    // Filter UI-style and implicit attributes
+    return FilterAttributes(dest);
+}
+
+void UIElement::Update(float timeStep)
+{
+}
+
+void UIElement::GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor)
+{
+    // Reset hovering for next frame
+    hovering_ = false;
+}
+
+void UIElement::GetDebugDrawBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor)
+{
+    SystemUIBatch batch(this, BLEND_ALPHA, currentScissor, 0, &vertexData);
+
+    int horizontalThickness = 1;
+    int verticalThickness = 1;
+    if (parent_)
+    {
+        switch (parent_->layoutMode_)
+        {
+        case LM_HORIZONTAL:
+            verticalThickness += 2;
+            break;
+
+        case LM_VERTICAL:
+            horizontalThickness += 2;
+            break;
+
+        default:
+            break;
+        }
+    }
+
+    batch.SetColor(Color::BLUE, true);
+    // Left
+    batch.AddQuad(0, 0, horizontalThickness, size_.y_, 0, 0);
+    // Top
+    batch.AddQuad(0, 0, size_.x_, verticalThickness, 0, 0);
+    // Right
+    batch.AddQuad(size_.x_ - horizontalThickness, 0, horizontalThickness, size_.y_, 0, 0);
+    // Bottom
+    batch.AddQuad(0, size_.y_ - verticalThickness, size_.x_, verticalThickness, 0, 0);
+
+    SystemUIBatch::AddOrMerge(batch, batches);
+}
+
+bool UIElement::IsWithinScissor(const IntRect& currentScissor)
+{
+    if (!visible_)
+        return false;
+
+    const IntVector2& screenPos = GetScreenPosition();
+    return screenPos.x_ < currentScissor.right_ && screenPos.x_ + GetWidth() > currentScissor.left_ &&
+           screenPos.y_ < currentScissor.bottom_ && screenPos.y_ + GetHeight() > currentScissor.top_;
+}
+
+const IntVector2& UIElement::GetScreenPosition() const
+{
+    if (positionDirty_)
+    {
+        IntVector2 pos = position_;
+        const UIElement* parent = parent_;
+
+        if (parent)
+        {
+            const IntVector2& parentScreenPos = parent->GetScreenPosition();
+
+            switch (horizontalAlignment_)
+            {
+            case HA_LEFT:
+                pos.x_ += parentScreenPos.x_;
+                break;
+
+            case HA_CENTER:
+                pos.x_ += parentScreenPos.x_ + parent_->size_.x_ / 2 - size_.x_ / 2;
+                break;
+
+            case HA_RIGHT:
+                pos.x_ += parentScreenPos.x_ + parent_->size_.x_ - size_.x_;
+                break;
+            }
+            switch (verticalAlignment_)
+            {
+            case VA_TOP:
+                pos.y_ += parentScreenPos.y_;
+                break;
+
+            case VA_CENTER:
+                pos.y_ += parentScreenPos.y_ + parent_->size_.y_ / 2 - size_.y_ / 2;
+                break;
+
+            case VA_BOTTOM:
+                pos.y_ += parentScreenPos.y_ + parent_->size_.y_ - size_.y_;
+                break;
+            }
+
+            pos += parent_->childOffset_;
+        }
+
+        screenPosition_ = pos;
+        positionDirty_ = false;
+    }
+
+    return screenPosition_;
+}
+
+void UIElement::OnHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
+{
+    hovering_ = true;
+}
+
+void UIElement::OnClickBegin(const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers,
+    Cursor* cursor)
+{
+}
+
+void UIElement::OnClickEnd(const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers,
+    Cursor* cursor, UIElement* beginElement)
+{
+}
+
+void UIElement::OnDoubleClick(const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers,
+    Cursor* cursor)
+{
+}
+
+void UIElement::OnDragBegin(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers,
+    Cursor* cursor)
+{
+    dragButtonCombo_ = buttons;
+    dragButtonCount_ = CountSetBits((unsigned)dragButtonCombo_);
+}
+
+void UIElement::OnDragMove(const IntVector2& position, const IntVector2& screenPosition, const IntVector2& deltaPos, int buttons,
+    int qualifiers, Cursor* cursor)
+{
+}
+
+void UIElement::OnDragEnd(const IntVector2& position, const IntVector2& screenPosition, int dragButtons, int buttons,
+    Cursor* cursor)
+{
+    dragButtonCombo_ = 0;
+    dragButtonCount_ = 0;
+}
+
+void UIElement::OnDragCancel(const IntVector2& position, const IntVector2& screenPosition, int dragButtons, int buttons,
+    Cursor* cursor)
+{
+    dragButtonCombo_ = 0;
+    dragButtonCount_ = 0;
+}
+
+bool UIElement::OnDragDropTest(UIElement* source)
+{
+    return true;
+}
+
+bool UIElement::OnDragDropFinish(UIElement* source)
+{
+    return true;
+}
+
+void UIElement::OnWheel(int delta, int buttons, int qualifiers)
+{
+}
+
+void UIElement::OnKey(int key, int buttons, int qualifiers)
+{
+}
+
+void UIElement::OnTextInput(const String& text, int buttons, int qualifiers)
+{
+}
+
+bool UIElement::LoadXML(Deserializer& source)
+{
+    SharedPtr<XMLFile> xml(new XMLFile(context_));
+    return xml->Load(source) && LoadXML(xml->GetRoot());
+}
+
+bool UIElement::SaveXML(Serializer& dest, const String& indentation) const
+{
+    SharedPtr<XMLFile> xml(new XMLFile(context_));
+    XMLElement rootElem = xml->CreateRoot("element");
+    return SaveXML(rootElem) && xml->Save(dest, indentation);
+}
+
+bool UIElement::FilterAttributes(XMLElement& dest) const
+{
+    // Filter UI styling attributes
+    XMLFile* styleFile = GetDefaultStyle();
+    if (styleFile)
+    {
+        String style = dest.GetAttribute("style");
+        if (!style.Empty() && style != "none")
+        {
+            if (styleXPathQuery_.SetVariable("typeName", style))
+            {
+                XMLElement styleElem = GetDefaultStyle()->GetRoot().SelectSinglePrepared(styleXPathQuery_);
+                if (styleElem && !FilterUIStyleAttributes(dest, styleElem))
+                    return false;
+            }
+        }
+    }
+
+    // Filter implicit attributes
+    if (!FilterImplicitAttributes(dest))
+    {
+        LOGERROR("Could not remove implicit attributes");
+        return false;
+    }
+
+    return true;
+}
+
+void UIElement::SetName(const String& name)
+{
+    name_ = name;
+
+    using namespace NameChanged;
+
+    VariantMap& eventData = GetEventDataMap();
+    eventData[P_ELEMENT] = this;
+
+    SendEvent(E_NAMECHANGED, eventData);
+}
+
+void UIElement::SetPosition(const IntVector2& position)
+{
+    if (position != position_)
+    {
+        position_ = position;
+        OnPositionSet();
+        MarkDirty();
+
+        using namespace Positioned;
+
+        VariantMap& eventData = GetEventDataMap();
+        eventData[P_ELEMENT] = this;
+        eventData[P_X] = position_.x_;
+        eventData[P_Y] = position_.y_;
+        SendEvent(E_POSITIONED, eventData);
+    }
+}
+
+void UIElement::SetPosition(int x, int y)
+{
+    SetPosition(IntVector2(x, y));
+}
+
+void UIElement::SetSize(const IntVector2& size)
+{
+    ++resizeNestingLevel_;
+
+    IntVector2 validatedSize;
+    validatedSize.x_ = Clamp(size.x_, minSize_.x_, maxSize_.x_);
+    validatedSize.y_ = Clamp(size.y_, minSize_.y_, maxSize_.y_);
+
+    if (validatedSize != size_)
+    {
+        size_ = validatedSize;
+
+        if (resizeNestingLevel_ == 1)
+        {
+            // Check if parent element's layout needs to be updated first
+            if (parent_)
+                parent_->UpdateLayout();
+
+            MarkDirty();
+            OnResize();
+            UpdateLayout();
+
+            using namespace Resized;
+
+            VariantMap& eventData = GetEventDataMap();
+            eventData[P_ELEMENT] = this;
+            eventData[P_WIDTH] = size_.x_;
+            eventData[P_HEIGHT] = size_.y_;
+            SendEvent(E_RESIZED, eventData);
+        }
+    }
+
+    --resizeNestingLevel_;
+}
+
+void UIElement::SetSize(int width, int height)
+{
+    SetSize(IntVector2(width, height));
+}
+
+void UIElement::SetWidth(int width)
+{
+    SetSize(IntVector2(width, size_.y_));
+}
+
+void UIElement::SetHeight(int height)
+{
+    SetSize(IntVector2(size_.x_, height));
+}
+
+void UIElement::SetMinSize(const IntVector2& minSize)
+{
+    minSize_.x_ = Max(minSize.x_, 0);
+    minSize_.y_ = Max(minSize.y_, 0);
+    SetSize(size_);
+}
+
+void UIElement::SetMinSize(int width, int height)
+{
+    SetMinSize(IntVector2(width, height));
+}
+
+void UIElement::SetMinWidth(int width)
+{
+    SetMinSize(IntVector2(width, minSize_.y_));
+}
+
+void UIElement::SetMinHeight(int height)
+{
+    SetMinSize(IntVector2(minSize_.x_, height));
+}
+
+void UIElement::SetMaxSize(const IntVector2& maxSize)
+{
+    maxSize_.x_ = Max(maxSize.x_, 0);
+    maxSize_.y_ = Max(maxSize.y_, 0);
+    SetSize(size_);
+}
+
+void UIElement::SetMaxSize(int width, int height)
+{
+    SetMaxSize(IntVector2(width, height));
+}
+
+void UIElement::SetMaxWidth(int width)
+{
+    SetMaxSize(IntVector2(width, maxSize_.y_));
+}
+
+void UIElement::SetMaxHeight(int height)
+{
+    SetMaxSize(IntVector2(maxSize_.x_, height));
+}
+
+void UIElement::SetFixedSize(const IntVector2& size)
+{
+    minSize_ = maxSize_ = IntVector2(Max(size.x_, 0), Max(size.y_, 0));
+    SetSize(size);
+}
+
+void UIElement::SetFixedSize(int width, int height)
+{
+    SetFixedSize(IntVector2(width, height));
+}
+
+void UIElement::SetFixedWidth(int width)
+{
+    minSize_.x_ = maxSize_.x_ = Max(width, 0);
+    SetWidth(width);
+}
+
+void UIElement::SetFixedHeight(int height)
+{
+    minSize_.y_ = maxSize_.y_ = Max(height, 0);
+    SetHeight(height);
+}
+
+void UIElement::SetAlignment(HorizontalAlignment hAlign, VerticalAlignment vAlign)
+{
+    SetHorizontalAlignment(hAlign);
+    SetVerticalAlignment(vAlign);
+}
+
+void UIElement::SetHorizontalAlignment(HorizontalAlignment align)
+{
+    if (align != HA_LEFT && parent_ && parent_->GetLayoutMode() == LM_HORIZONTAL)
+    {
+        LOGWARNING("Forcing left alignment because parent element has horizontal layout");
+        align = HA_LEFT;
+    }
+
+    if (horizontalAlignment_ != align)
+    {
+        horizontalAlignment_ = align;
+        MarkDirty();
+    }
+}
+
+void UIElement::SetVerticalAlignment(VerticalAlignment align)
+{
+    if (align != VA_TOP && parent_ && parent_->GetLayoutMode() == LM_VERTICAL)
+    {
+        LOGWARNING("Forcing top alignment because parent element has vertical layout");
+        align = VA_TOP;
+    }
+
+    if (verticalAlignment_ != align)
+    {
+        verticalAlignment_ = align;
+        MarkDirty();
+    }
+}
+
+void UIElement::SetClipBorder(const IntRect& rect)
+{
+    clipBorder_.left_ = Max(rect.left_, 0);
+    clipBorder_.top_ = Max(rect.top_, 0);
+    clipBorder_.right_ = Max(rect.right_, 0);
+    clipBorder_.bottom_ = Max(rect.bottom_, 0);
+}
+
+void UIElement::SetColor(const Color& color)
+{
+    for (unsigned i = 0; i < MAX_UIELEMENT_CORNERS; ++i)
+        color_[i] = color;
+    colorGradient_ = false;
+    derivedColorDirty_ = true;
+}
+
+void UIElement::SetColor(Corner corner, const Color& color)
+{
+    color_[corner] = color;
+    colorGradient_ = false;
+    derivedColorDirty_ = true;
+
+    for (unsigned i = 0; i < MAX_UIELEMENT_CORNERS; ++i)
+    {
+        if (i != corner && color_[i] != color_[corner])
+            colorGradient_ = true;
+    }
+}
+
+void UIElement::SetPriority(int priority)
+{
+    priority_ = priority;
+    if (parent_)
+        parent_->sortOrderDirty_ = true;
+}
+
+void UIElement::SetOpacity(float opacity)
+{
+    opacity_ = Clamp(opacity, 0.0f, 1.0f);
+    MarkDirty();
+}
+
+void UIElement::SetBringToFront(bool enable)
+{
+    bringToFront_ = enable;
+}
+
+void UIElement::SetBringToBack(bool enable)
+{
+    bringToBack_ = enable;
+}
+
+void UIElement::SetClipChildren(bool enable)
+{
+    clipChildren_ = enable;
+}
+
+void UIElement::SetSortChildren(bool enable)
+{
+    if (!sortChildren_ && enable)
+        sortOrderDirty_ = true;
+
+    sortChildren_ = enable;
+}
+
+void UIElement::SetUseDerivedOpacity(bool enable)
+{
+    useDerivedOpacity_ = enable;
+}
+
+void UIElement::SetEnabled(bool enable)
+{
+    enabled_ = enable;
+    enabledPrev_ = enable;
+}
+
+void UIElement::SetDeepEnabled(bool enable)
+{
+    enabled_ = enable;
+
+    for (Vector<SharedPtr<UIElement> >::ConstIterator i = children_.Begin(); i != children_.End(); ++i)
+        (*i)->SetDeepEnabled(enable);
+}
+
+void UIElement::ResetDeepEnabled()
+{
+    enabled_ = enabledPrev_;
+
+    for (Vector<SharedPtr<UIElement> >::ConstIterator i = children_.Begin(); i != children_.End(); ++i)
+        (*i)->ResetDeepEnabled();
+}
+
+void UIElement::SetEnabledRecursive(bool enable)
+{
+    enabled_ = enable;
+    enabledPrev_ = enable;
+
+    for (Vector<SharedPtr<UIElement> >::ConstIterator i = children_.Begin(); i != children_.End(); ++i)
+        (*i)->SetEnabledRecursive(enable);
+}
+
+void UIElement::SetEditable(bool enable)
+{
+    editable_ = enable;
+    OnSetEditable();
+}
+
+void UIElement::SetFocusMode(FocusMode mode)
+{
+    focusMode_ = mode;
+}
+
+void UIElement::SetFocus(bool enable)
+{
+    // Invisible elements should not receive focus
+    if (focusMode_ < FM_FOCUSABLE || !IsVisibleEffective())
+        enable = false;
+
+    SystemUI* ui = GetSubsystem<SystemUI>();
+    if (enable)
+    {
+        if (ui->GetFocusElement() != this)
+            ui->SetFocusElement(this);
+    }
+    else
+    {
+        if (ui->GetFocusElement() == this)
+            ui->SetFocusElement(0);
+    }
+}
+
+void UIElement::SetSelected(bool enable)
+{
+    selected_ = enable;
+}
+
+void UIElement::SetVisible(bool enable)
+{
+    if (enable != visible_)
+    {
+        visible_ = enable;
+
+        // Parent's layout may change as a result of visibility change
+        if (parent_)
+            parent_->UpdateLayout();
+
+        using namespace VisibleChanged;
+
+        VariantMap& eventData = GetEventDataMap();
+        eventData[P_ELEMENT] = this;
+        eventData[P_VISIBLE] = visible_;
+        SendEvent(E_VISIBLECHANGED, eventData);
+
+        // If the focus element becomes effectively hidden, clear focus
+        if (!enable)
+        {
+            UIElement* focusElement = GetSubsystem<SystemUI>()->GetFocusElement();
+            if (focusElement && !focusElement->IsVisibleEffective())
+                focusElement->SetFocus(false);
+        }
+    }
+}
+
+void UIElement::SetDragDropMode(unsigned mode)
+{
+    dragDropMode_ = mode;
+}
+
+bool UIElement::SetStyle(const String& styleName, XMLFile* file)
+{
+    // If empty style was requested, replace with type name
+    String actualStyleName = !styleName.Empty() ? styleName : GetTypeName();
+
+    appliedStyle_ = actualStyleName;
+    if (styleName == "none")
+        return true;
+
+    if (!file)
+    {
+        file = GetDefaultStyle();
+        if (!file)
+            return false;
+    }
+    else
+    {
+        // If a custom style file specified, remember it
+        defaultStyle_ = file;
+    }
+
+    styleXPathQuery_.SetVariable("typeName", actualStyleName);
+    XMLElement styleElem = file->GetRoot().SelectSinglePrepared(styleXPathQuery_);
+    return styleElem && SetStyle(styleElem);
+}
+
+bool UIElement::SetStyle(const XMLElement& element)
+{
+    appliedStyle_ = element.GetAttribute("type");
+
+    // Consider style attribute values as instance-level attribute default values
+    return LoadXML(element, true);
+}
+
+bool UIElement::SetStyleAuto(XMLFile* file)
+{
+    return SetStyle(String::EMPTY, file);
+}
+
+void UIElement::SetDefaultStyle(XMLFile* style)
+{
+    defaultStyle_ = style;
+}
+
+void UIElement::SetLayout(LayoutMode mode, int spacing, const IntRect& border)
+{
+    layoutMode_ = mode;
+    layoutSpacing_ = Max(spacing, 0);
+    layoutBorder_ = IntRect(Max(border.left_, 0), Max(border.top_, 0), Max(border.right_, 0), Max(border.bottom_, 0));
+    VerifyChildAlignment();
+    UpdateLayout();
+}
+
+void UIElement::SetLayoutMode(LayoutMode mode)
+{
+    layoutMode_ = mode;
+    VerifyChildAlignment();
+    UpdateLayout();
+}
+
+void UIElement::SetLayoutSpacing(int spacing)
+{
+    layoutSpacing_ = Max(spacing, 0);
+    UpdateLayout();
+}
+
+void UIElement::SetLayoutBorder(const IntRect& border)
+{
+    layoutBorder_ = IntRect(Max(border.left_, 0), Max(border.top_, 0), Max(border.right_, 0), Max(border.bottom_, 0));
+    UpdateLayout();
+}
+
+void UIElement::SetLayoutFlexScale(const Vector2& scale)
+{
+    layoutFlexScale_ = Vector2(Max(scale.x_, 0.0f), Max(scale.y_, 0.0f));
+}
+
+void UIElement::SetIndent(int indent)
+{
+    indent_ = indent;
+    if (parent_)
+        parent_->UpdateLayout();
+    UpdateLayout();
+    OnIndentSet();
+}
+
+void UIElement::SetIndentSpacing(int indentSpacing)
+{
+    indentSpacing_ = Max(indentSpacing, 0);
+    if (parent_)
+        parent_->UpdateLayout();
+    UpdateLayout();
+    OnIndentSet();
+}
+
+void UIElement::UpdateLayout()
+{
+    if (layoutMode_ == LM_FREE || layoutNestingLevel_)
+        return;
+
+    // Prevent further updates while this update happens
+    DisableLayoutUpdate();
+
+    PODVector<int> positions;
+    PODVector<int> sizes;
+    PODVector<int> minSizes;
+    PODVector<int> maxSizes;
+    PODVector<float> flexScales;
+
+    int baseIndentWidth = GetIndentWidth();
+
+    if (layoutMode_ == LM_HORIZONTAL)
+    {
+        int minChildHeight = 0;
+
+        for (unsigned i = 0; i < children_.Size(); ++i)
+        {
+            if (!children_[i]->IsVisible())
+                continue;
+            positions.Push(baseIndentWidth);
+            unsigned indent = (unsigned)children_[i]->GetIndentWidth();
+            sizes.Push(children_[i]->GetWidth() + indent);
+            minSizes.Push(children_[i]->GetMinWidth() + indent);
+            maxSizes.Push(children_[i]->GetMaxWidth() + indent);
+            flexScales.Push(children_[i]->GetLayoutFlexScale().x_);
+            minChildHeight = Max(minChildHeight, children_[i]->GetMinHeight());
+        }
+
+        CalculateLayout(positions, sizes, minSizes, maxSizes, flexScales, GetWidth(), layoutBorder_.left_, layoutBorder_.right_,
+            layoutSpacing_);
+
+        int width = CalculateLayoutParentSize(sizes, layoutBorder_.left_, layoutBorder_.right_, layoutSpacing_);
+        int height = Max(GetHeight(), minChildHeight + layoutBorder_.top_ + layoutBorder_.bottom_);
+        int minWidth =
+            Min(CalculateLayoutParentSize(minSizes, layoutBorder_.left_, layoutBorder_.right_, layoutSpacing_), maxSize_.x_);
+        int minHeight = Min(minChildHeight + layoutBorder_.top_ + layoutBorder_.bottom_, maxSize_.y_);
+        // Respect fixed size if already set
+        if (minSize_.x_ != maxSize_.x_)
+            minSize_.x_ = minWidth;
+        if (minSize_.y_ != maxSize_.y_)
+            minSize_.y_ = minHeight;
+        SetSize(width, height);
+        // Validate the size before resizing child elements, in case of min/max limits
+        width = size_.x_;
+        height = size_.y_;
+
+        unsigned j = 0;
+        for (unsigned i = 0; i < children_.Size(); ++i)
+        {
+            if (!children_[i]->IsVisible())
+                continue;
+            children_[i]->SetPosition(positions[j], GetLayoutChildPosition(children_[i]).y_);
+            children_[i]->SetSize(sizes[j], height - layoutBorder_.top_ - layoutBorder_.bottom_);
+            ++j;
+        }
+    }
+    else if (layoutMode_ == LM_VERTICAL)
+    {
+        int minChildWidth = 0;
+
+        for (unsigned i = 0; i < children_.Size(); ++i)
+        {
+            if (!children_[i]->IsVisible())
+                continue;
+            positions.Push(0);
+            sizes.Push(children_[i]->GetHeight());
+            minSizes.Push(children_[i]->GetMinHeight());
+            maxSizes.Push(children_[i]->GetMaxHeight());
+            flexScales.Push(children_[i]->GetLayoutFlexScale().y_);
+            minChildWidth = Max(minChildWidth, children_[i]->GetMinWidth() + children_[i]->GetIndentWidth());
+        }
+
+        CalculateLayout(positions, sizes, minSizes, maxSizes, flexScales, GetHeight(), layoutBorder_.top_, layoutBorder_.bottom_,
+            layoutSpacing_);
+
+        int height = CalculateLayoutParentSize(sizes, layoutBorder_.top_, layoutBorder_.bottom_, layoutSpacing_);
+        int width = Max(GetWidth(), minChildWidth + layoutBorder_.left_ + layoutBorder_.right_);
+        int minHeight =
+            Min(CalculateLayoutParentSize(minSizes, layoutBorder_.top_, layoutBorder_.bottom_, layoutSpacing_), maxSize_.y_);
+        int minWidth = Min(minChildWidth + layoutBorder_.left_ + layoutBorder_.right_, maxSize_.x_);
+        if (minSize_.x_ != maxSize_.x_)
+            minSize_.x_ = minWidth;
+        if (minSize_.y_ != maxSize_.y_)
+            minSize_.y_ = minHeight;
+        SetSize(width, height);
+        width = size_.x_;
+        height = size_.y_;
+
+        unsigned j = 0;
+        for (unsigned i = 0; i < children_.Size(); ++i)
+        {
+            if (!children_[i]->IsVisible())
+                continue;
+            children_[i]->SetPosition(GetLayoutChildPosition(children_[i]).x_ + baseIndentWidth, positions[j]);
+            children_[i]->SetSize(width - layoutBorder_.left_ - layoutBorder_.right_, sizes[j]);
+            ++j;
+        }
+    }
+
+    using namespace LayoutUpdated;
+
+    VariantMap& eventData = GetEventDataMap();
+    eventData[P_ELEMENT] = this;
+    SendEvent(E_LAYOUTUPDATED, eventData);
+
+    EnableLayoutUpdate();
+}
+
+void UIElement::DisableLayoutUpdate()
+{
+    ++layoutNestingLevel_;
+}
+
+void UIElement::EnableLayoutUpdate()
+{
+    --layoutNestingLevel_;
+}
+
+void UIElement::BringToFront()
+{
+    // Follow the parent chain to the top level window. If it has BringToFront mode, bring it to front now
+    UIElement* root = GetRoot();
+    // If element is detached from hierarchy, this must be a no-op
+    if (!root)
+        return;
+
+    UIElement* ptr = this;
+    while (ptr && ptr->GetParent() != root)
+        ptr = ptr->GetParent();
+    if (!ptr || !ptr->GetBringToFront())
+        return;
+
+    // Get the highest priority used by all other top level elements, assign that to the new front element
+    // and decrease others' priority where necessary. However, take into account only input-enabled
+    // elements and those which have the BringToBack flag set
+    HashSet<int> usedPriorities;
+
+    int maxPriority = M_MIN_INT;
+    const Vector<SharedPtr<UIElement> >& rootChildren = root->GetChildren();
+    for (Vector<SharedPtr<UIElement> >::ConstIterator i = rootChildren.Begin(); i != rootChildren.End(); ++i)
+    {
+        UIElement* other = *i;
+        if (other->IsEnabled() && other->bringToBack_ && other != ptr)
+        {
+            int priority = other->GetPriority();
+            // M_MAX_INT is used by popups and tooltips. Disregard these to avoid an "arms race" with the priorities
+            if (priority == M_MAX_INT)
+                continue;
+            usedPriorities.Insert(priority);
+            maxPriority = Max(priority, maxPriority);
+        }
+    }
+
+    if (maxPriority != M_MIN_INT && maxPriority >= ptr->GetPriority())
+    {
+        ptr->SetPriority(maxPriority);
+
+        int minPriority = maxPriority;
+        while (usedPriorities.Contains(minPriority))
+            --minPriority;
+
+        for (Vector<SharedPtr<UIElement> >::ConstIterator i = rootChildren.Begin(); i != rootChildren.End(); ++i)
+        {
+            UIElement* other = *i;
+            int priority = other->GetPriority();
+
+            if (other->IsEnabled() && other->bringToBack_ && other != ptr && priority >= minPriority && priority <= maxPriority)
+                other->SetPriority(priority - 1);
+        }
+    }
+}
+
+UIElement* UIElement::CreateChild(StringHash type, const String& name, unsigned index)
+{
+    // Check that creation succeeds and that the object in fact is a UI element
+    SharedPtr<UIElement> newElement = DynamicCast<UIElement>(context_->CreateObject(type));
+    if (!newElement)
+    {
+        LOGERROR("Could not create unknown UI element type " + type.ToString());
+        return 0;
+    }
+
+    if (!name.Empty())
+        newElement->SetName(name);
+
+    InsertChild(index, newElement);
+    return newElement;
+}
+
+void UIElement::AddChild(UIElement* element)
+{
+    InsertChild(M_MAX_UNSIGNED, element);
+}
+
+void UIElement::InsertChild(unsigned index, UIElement* element)
+{
+    // Check for illegal or redundant parent assignment
+    if (!element || element == this || element->parent_ == this)
+        return;
+    // Check for possible cyclic parent assignment
+    UIElement* parent = parent_;
+    while (parent)
+    {
+        if (parent == element)
+            return;
+        parent = parent->parent_;
+    }
+
+    // Add first, then remove from old parent, to ensure the element does not get deleted
+    if (index >= children_.Size())
+        children_.Push(SharedPtr<UIElement>(element));
+    else
+        children_.Insert(children_.Begin() + index, SharedPtr<UIElement>(element));
+
+    XMLFile* previousStyleFile = element->GetDefaultStyle();
+
+    element->Remove();
+
+    if (sortChildren_)
+        sortOrderDirty_ = true;
+
+    element->parent_ = this;
+    element->MarkDirty();
+
+    // If child element did not already have a style file, but has specified a style name, apply it now
+    if (!previousStyleFile && !element->appliedStyle_.Empty() && GetDefaultStyle())
+        element->SetStyle(element->appliedStyle_);
+
+    VerifyChildAlignment();
+    UpdateLayout();
+
+    // Send change event
+    UIElement* root = GetRoot();
+    UIElement* sender = GetElementEventSender();
+    if (sender)
+    {
+        using namespace ElementAdded;
+
+        VariantMap& eventData = GetEventDataMap();
+        eventData[P_ROOT] = root;
+        eventData[P_PARENT] = this;
+        eventData[P_ELEMENT] = element;
+
+        sender->SendEvent(E_ELEMENTADDED, eventData);
+    }
+}
+
+void UIElement::RemoveChild(UIElement* element, unsigned index)
+{
+    for (unsigned i = index; i < children_.Size(); ++i)
+    {
+        if (children_[i] == element)
+        {
+            // Send change event if not already being destroyed
+            UIElement* sender = Refs() > 0 ? GetElementEventSender() : 0;
+            if (sender)
+            {
+                using namespace ElementRemoved;
+
+                VariantMap& eventData = GetEventDataMap();
+                eventData[P_ROOT] = GetRoot();
+                eventData[P_PARENT] = this;
+                eventData[P_ELEMENT] = element;
+
+                sender->SendEvent(E_ELEMENTREMOVED, eventData);
+            }
+
+            element->Detach();
+            children_.Erase(i);
+            UpdateLayout();
+            return;
+        }
+    }
+}
+
+void UIElement::RemoveChildAtIndex(unsigned index)
+{
+    if (index >= children_.Size())
+        return;
+
+    // Send change event if not already being destroyed
+    UIElement* sender = Refs() > 0 ? GetElementEventSender() : 0;
+    if (sender)
+    {
+        using namespace ElementRemoved;
+
+        VariantMap& eventData = GetEventDataMap();
+        eventData[P_ROOT] = GetRoot();
+        eventData[P_PARENT] = this;
+        eventData[P_ELEMENT] = children_[index];
+
+        sender->SendEvent(E_ELEMENTREMOVED, eventData);
+    }
+
+    children_[index]->Detach();
+    children_.Erase(index);
+    UpdateLayout();
+}
+
+void UIElement::RemoveAllChildren()
+{
+    UIElement* root = GetRoot();
+    UIElement* sender = Refs() > 0 ? GetElementEventSender() : 0;
+
+    for (Vector<SharedPtr<UIElement> >::Iterator i = children_.Begin(); i < children_.End();)
+    {
+        // Send change event if not already being destroyed
+        if (sender)
+        {
+            using namespace ElementRemoved;
+
+            VariantMap& eventData = GetEventDataMap();
+            eventData[P_ROOT] = root;
+            eventData[P_PARENT] = this;
+            eventData[P_ELEMENT] = (*i).Get();
+
+            sender->SendEvent(E_ELEMENTREMOVED, eventData);
+        }
+
+        (*i++)->Detach();
+    }
+    children_.Clear();
+    UpdateLayout();
+}
+
+void UIElement::Remove()
+{
+    if (parent_)
+        parent_->RemoveChild(this);
+}
+
+unsigned UIElement::FindChild(UIElement* element) const
+{
+    Vector<SharedPtr<UIElement> >::ConstIterator i = children_.Find(SharedPtr<UIElement>(element));
+    return i != children_.End() ? (unsigned)(i - children_.Begin()) : M_MAX_UNSIGNED;
+}
+
+void UIElement::SetParent(UIElement* parent, unsigned index)
+{
+    if (parent)
+        parent->InsertChild(index, this);
+}
+
+void UIElement::SetVar(StringHash key, const Variant& value)
+{
+    vars_[key] = value;
+}
+
+void UIElement::SetInternal(bool enable)
+{
+    internal_ = enable;
+}
+
+void UIElement::SetTraversalMode(TraversalMode traversalMode)
+{
+    traversalMode_ = traversalMode;
+}
+
+void UIElement::SetElementEventSender(bool flag)
+{
+    elementEventSender_ = flag;
+}
+
+float UIElement::GetDerivedOpacity() const
+{
+    if (!useDerivedOpacity_)
+        return opacity_;
+
+    if (opacityDirty_)
+    {
+        derivedOpacity_ = opacity_;
+        const UIElement* parent = parent_;
+
+        while (parent)
+        {
+            derivedOpacity_ *= parent->opacity_;
+            parent = parent->parent_;
+        }
+
+        opacityDirty_ = false;
+    }
+
+    return derivedOpacity_;
+}
+
+bool UIElement::HasFocus() const
+{
+    return GetSubsystem<SystemUI>()->GetFocusElement() == this;
+}
+
+bool UIElement::IsVisibleEffective() const
+{
+    bool visible = visible_;
+    const UIElement* element = parent_;
+    
+    // Traverse the parent chain
+    while (visible && element)
+    {
+        visible &= element->visible_;
+        element = element->parent_;
+    }
+    
+    return visible;
+}
+
+const String& UIElement::GetAppliedStyle() const
+{
+    return appliedStyle_ == GetTypeName() ? String::EMPTY : appliedStyle_;
+}
+
+XMLFile* UIElement::GetDefaultStyle(bool recursiveUp) const
+{
+    if (recursiveUp)
+    {
+        const UIElement* element = this;
+        while (element)
+        {
+            if (element->defaultStyle_)
+                return element->defaultStyle_;
+            element = element->parent_;
+        }
+        return 0;
+    }
+    else
+        return defaultStyle_;
+}
+
+void UIElement::GetChildren(PODVector<UIElement*>& dest, bool recursive) const
+{
+    dest.Clear();
+
+    if (!recursive)
+    {
+        dest.Reserve(children_.Size());
+        for (Vector<SharedPtr<UIElement> >::ConstIterator i = children_.Begin(); i != children_.End(); ++i)
+            dest.Push(*i);
+    }
+    else
+        GetChildrenRecursive(dest);
+}
+
+unsigned UIElement::GetNumChildren(bool recursive) const
+{
+    if (!recursive)
+        return children_.Size();
+    else
+    {
+        unsigned allChildren = children_.Size();
+        for (Vector<SharedPtr<UIElement> >::ConstIterator i = children_.Begin(); i != children_.End(); ++i)
+            allChildren += (*i)->GetNumChildren(true);
+
+        return allChildren;
+    }
+}
+
+UIElement* UIElement::GetChild(unsigned index) const
+{
+    return index < children_.Size() ? children_[index] : (UIElement*)0;
+}
+
+UIElement* UIElement::GetChild(const String& name, bool recursive) const
+{
+    for (Vector<SharedPtr<UIElement> >::ConstIterator i = children_.Begin(); i != children_.End(); ++i)
+    {
+        if ((*i)->name_ == name)
+            return *i;
+
+        if (recursive)
+        {
+            UIElement* element = (*i)->GetChild(name, true);
+            if (element)
+                return element;
+        }
+    }
+
+    return 0;
+}
+
+UIElement* UIElement::GetChild(const StringHash& key, const Variant& value, bool recursive) const
+{
+    for (Vector<SharedPtr<UIElement> >::ConstIterator i = children_.Begin(); i != children_.End(); ++i)
+    {
+        const Variant& varValue = (*i)->GetVar(key);
+        if (value != Variant::EMPTY ? varValue == value : varValue != Variant::EMPTY)
+            return *i;
+
+        if (recursive)
+        {
+            UIElement* element = (*i)->GetChild(key, value, true);
+            if (element)
+                return element;
+        }
+    }
+
+    return 0;
+}
+
+UIElement* UIElement::GetRoot() const
+{
+    UIElement* root = parent_;
+    if (!root)
+        return 0;
+    while (root->GetParent())
+        root = root->GetParent();
+    return root;
+}
+
+const Color& UIElement::GetDerivedColor() const
+{
+    if (derivedColorDirty_)
+    {
+        derivedColor_ = color_[C_TOPLEFT];
+        derivedColor_.a_ *= GetDerivedOpacity();
+        derivedColorDirty_ = false;
+    }
+
+    return derivedColor_;
+}
+
+const Variant& UIElement::GetVar(const StringHash& key) const
+{
+    VariantMap::ConstIterator i = vars_.Find(key);
+    return i != vars_.End() ? i->second_ : Variant::EMPTY;
+}
+
+IntVector2 UIElement::ScreenToElement(const IntVector2& screenPosition)
+{
+    return screenPosition - GetScreenPosition();
+}
+
+IntVector2 UIElement::ElementToScreen(const IntVector2& position)
+{
+    return position + GetScreenPosition();
+}
+
+bool UIElement::IsInside(IntVector2 position, bool isScreen)
+{
+    if (isScreen)
+        position = ScreenToElement(position);
+    return position.x_ >= 0 && position.y_ >= 0 && position.x_ < size_.x_ && position.y_ < size_.y_;
+}
+
+bool UIElement::IsInsideCombined(IntVector2 position, bool isScreen)
+{
+    // If child elements are clipped, no need to expand the rect
+    if (clipChildren_)
+        return IsInside(position, isScreen);
+
+    if (!isScreen)
+        position = ElementToScreen(position);
+
+    IntRect combined = GetCombinedScreenRect();
+    return position.x_ >= combined.left_ && position.y_ >= combined.top_ && position.x_ < combined.right_ &&
+           position.y_ < combined.bottom_;
+}
+
+IntRect UIElement::GetCombinedScreenRect()
+{
+    IntVector2 screenPosition(GetScreenPosition());
+    IntRect combined(screenPosition.x_, screenPosition.y_, screenPosition.x_ + size_.x_, screenPosition.y_ + size_.y_);
+
+    if (!clipChildren_)
+    {
+        for (Vector<SharedPtr<UIElement> >::Iterator i = children_.Begin(); i != children_.End(); ++i)
+        {
+            IntVector2 childPos = (*i)->GetScreenPosition();
+            const IntVector2& childSize = (*i)->GetSize();
+            if (childPos.x_ < combined.left_)
+                combined.left_ = childPos.x_;
+            if (childPos.y_ < combined.top_)
+                combined.top_ = childPos.y_;
+            if (childPos.x_ + childSize.x_ > combined.right_)
+                combined.right_ = childPos.x_ + childSize.x_;
+            if (childPos.y_ + childSize.y_ > combined.bottom_)
+                combined.bottom_ = childPos.y_ + childSize.y_;
+        }
+    }
+
+    return combined;
+}
+
+void UIElement::SortChildren()
+{
+    if (sortChildren_ && sortOrderDirty_)
+    {
+        // Only sort when there is no layout
+        if (layoutMode_ == LM_FREE)
+            Sort(children_.Begin(), children_.End(), CompareUIElements);
+        sortOrderDirty_ = false;
+    }
+}
+
+void UIElement::SetChildOffset(const IntVector2& offset)
+{
+    if (offset != childOffset_)
+    {
+        childOffset_ = offset;
+        for (Vector<SharedPtr<UIElement> >::ConstIterator i = children_.Begin(); i != children_.End(); ++i)
+            (*i)->MarkDirty();
+    }
+}
+
+void UIElement::SetHovering(bool enable)
+{
+    hovering_ = enable;
+}
+
+void UIElement::AdjustScissor(IntRect& currentScissor)
+{
+    if (clipChildren_)
+    {
+        IntVector2 screenPos = GetScreenPosition();
+        currentScissor.left_ = Max(currentScissor.left_, screenPos.x_ + clipBorder_.left_);
+        currentScissor.top_ = Max(currentScissor.top_, screenPos.y_ + clipBorder_.top_);
+        currentScissor.right_ = Min(currentScissor.right_, screenPos.x_ + size_.x_ - clipBorder_.right_);
+        currentScissor.bottom_ = Min(currentScissor.bottom_, screenPos.y_ + size_.y_ - clipBorder_.bottom_);
+
+        if (currentScissor.right_ < currentScissor.left_)
+            currentScissor.right_ = currentScissor.left_;
+        if (currentScissor.bottom_ < currentScissor.top_)
+            currentScissor.bottom_ = currentScissor.top_;
+    }
+}
+
+void UIElement::GetBatchesWithOffset(IntVector2& offset, PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData,
+    IntRect currentScissor)
+{
+    Vector2 floatOffset((float)offset.x_, (float)offset.y_);
+    unsigned initialSize = vertexData.Size();
+
+    GetBatches(batches, vertexData, currentScissor);
+    for (unsigned i = initialSize; i < vertexData.Size(); i += 6)
+    {
+        vertexData[i] += floatOffset.x_;
+        vertexData[i + 1] += floatOffset.y_;
+    }
+
+    AdjustScissor(currentScissor);
+    for (Vector<SharedPtr<UIElement> >::ConstIterator i = children_.Begin(); i != children_.End(); ++i)
+    {
+        if ((*i)->IsVisible())
+            (*i)->GetBatchesWithOffset(offset, batches, vertexData, currentScissor);
+    }
+}
+
+UIElement* UIElement::GetElementEventSender() const
+{
+    UIElement* element = const_cast<UIElement*>(this);
+    if (elementEventSender_)
+        return element;
+
+    while (element->parent_)
+    {
+        element = element->parent_;
+        if (element->elementEventSender_)
+            return element;
+    }
+
+    // If no predefined element event sender in the parental chain, return ultimate root element
+    return element;
+}
+
+void UIElement::OnAttributeAnimationAdded()
+{
+    if (attributeAnimationInfos_.Size() == 1)
+        SubscribeToEvent(E_POSTUPDATE, HANDLER(UIElement, HandlePostUpdate));
+}
+
+void UIElement::OnAttributeAnimationRemoved()
+{
+    if (attributeAnimationInfos_.Empty())
+        UnsubscribeFromEvent(E_POSTUPDATE);
+}
+
+void UIElement::SetObjectAttributeAnimation(const String& name, ValueAnimation* attributeAnimation, WrapMode wrapMode, float speed)
+{
+    Vector<String> names = name.Split('/');
+    // Only attribute name
+    if (names.Size() == 1)
+        SetAttributeAnimation(name, attributeAnimation, wrapMode, speed);
+    else
+    {
+        // Name must in following format: "#0/#1/attribute"
+        UIElement* element = this;
+        for (unsigned i = 0; i < names.Size() - 1; ++i)
+        {
+            if (names[i].Front() != '#')
+            {
+                LOGERROR("Invalid name " + name);
+                return;
+            }
+
+            unsigned index = (unsigned)ToInt(names[i].Substring(1, names[i].Length() - 1));
+            element = element->GetChild(index);
+            if (!element)
+            {
+                LOGERROR("Could not find element by name " + name);
+                return;
+            }
+        }
+
+        element->SetAttributeAnimation(names.Back(), attributeAnimation, wrapMode, speed);
+    }
+}
+
+void UIElement::MarkDirty()
+{
+    positionDirty_ = true;
+    opacityDirty_ = true;
+    derivedColorDirty_ = true;
+
+    for (Vector<SharedPtr<UIElement> >::ConstIterator i = children_.Begin(); i != children_.End(); ++i)
+        (*i)->MarkDirty();
+}
+
+bool UIElement::RemoveChildXML(XMLElement& parent, const String& name) const
+{
+    static XPathQuery matchXPathQuery("./attribute[@name=$attributeName]", "attributeName:String");
+
+    if (!matchXPathQuery.SetVariable("attributeName", name))
+        return false;
+
+    XMLElement removeElem = parent.SelectSinglePrepared(matchXPathQuery);
+    return !removeElem || parent.RemoveChild(removeElem);
+}
+
+bool UIElement::RemoveChildXML(XMLElement& parent, const String& name, const String& value) const
+{
+    static XPathQuery matchXPathQuery
+        ("./attribute[@name=$attributeName and @value=$attributeValue]", "attributeName:String, attributeValue:String");
+
+    if (!matchXPathQuery.SetVariable("attributeName", name))
+        return false;
+    if (!matchXPathQuery.SetVariable("attributeValue", value))
+        return false;
+
+    XMLElement removeElem = parent.SelectSinglePrepared(matchXPathQuery);
+    return !removeElem || parent.RemoveChild(removeElem);
+}
+
+bool UIElement::FilterUIStyleAttributes(XMLElement& dest, const XMLElement& styleElem) const
+{
+    // Remove style attribute only when its value is identical to the value stored in style file
+    String style = styleElem.GetAttribute("style");
+    if (!style.Empty())
+    {
+        if (style == dest.GetAttribute("style"))
+        {
+            if (!dest.RemoveAttribute("style"))
+            {
+                LOGWARNING("Could not remove style attribute");
+                return false;
+            }
+        }
+    }
+
+    // Perform the same action recursively for internal child elements stored in style file
+    XMLElement childDest = dest.GetChild("element");
+    XMLElement childElem = styleElem.GetChild("element");
+    while (childDest && childElem)
+    {
+        if (!childElem.GetBool("internal"))
+        {
+            LOGERROR("Invalid style file, style element can only contain internal child elements");
+            return false;
+        }
+        if (!FilterUIStyleAttributes(childDest, childElem))
+            return false;
+
+        childDest = childDest.GetNext("element");
+        childElem = childElem.GetNext("element");
+    }
+
+    // Remove style attribute when it is the same as its type, however, if it is an internal element then replace it to "none" instead
+    if (!dest.GetAttribute("style").Empty() && dest.GetAttribute("style") == dest.GetAttribute("type"))
+    {
+        if (internal_)
+        {
+            if (!dest.SetAttribute("style", "none"))
+                return false;
+        }
+        else
+        {
+            if (!dest.RemoveAttribute("style"))
+                return false;
+        }
+    }
+
+    return true;
+}
+
+bool UIElement::FilterImplicitAttributes(XMLElement& dest) const
+{
+    // Remove positioning and sizing attributes when they are under the influence of layout mode
+    if (layoutMode_ != LM_FREE && !IsFixedWidth() && !IsFixedHeight())
+    {
+        if (!RemoveChildXML(dest, "Min Size"))
+            return false;
+    }
+    if (parent_ && parent_->layoutMode_ != LM_FREE)
+    {
+        if (!RemoveChildXML(dest, "Position"))
+            return false;
+        if (!RemoveChildXML(dest, "Size"))
+            return false;
+    }
+
+    return true;
+}
+
+void UIElement::GetChildrenRecursive(PODVector<UIElement*>& dest) const
+{
+    for (Vector<SharedPtr<UIElement> >::ConstIterator i = children_.Begin(); i != children_.End(); ++i)
+    {
+        UIElement* element = *i;
+        dest.Push(element);
+        if (!element->children_.Empty())
+            element->GetChildrenRecursive(dest);
+    }
+}
+
+int UIElement::CalculateLayoutParentSize(const PODVector<int>& sizes, int begin, int end, int spacing)
+{
+    int width = begin + end;
+    if (sizes.Empty())
+        return width;
+
+    for (unsigned i = 0; i < sizes.Size(); ++i)
+    {
+        // If calculating maximum size, and the default is specified, do not overflow it
+        if (sizes[i] == M_MAX_INT)
+            return M_MAX_INT;
+        width += sizes[i] + spacing;
+    }
+    // The last spacing is not needed
+    return width - spacing;
+}
+
+void UIElement::CalculateLayout(PODVector<int>& positions, PODVector<int>& sizes, const PODVector<int>& minSizes,
+    const PODVector<int>& maxSizes, const PODVector<float>& flexScales, int targetSize, int begin, int end, int spacing)
+{
+    unsigned numChildren = sizes.Size();
+    if (!numChildren)
+        return;
+    int targetTotalSize = targetSize - begin - end - (numChildren - 1) * spacing;
+    if (targetTotalSize < 0)
+        targetTotalSize = 0;
+    int targetChildSize = targetTotalSize / numChildren;
+    int remainder = targetTotalSize % numChildren;
+    float add = (float)remainder / numChildren;
+    float acc = 0.0f;
+
+    // Initial pass
+    for (unsigned i = 0; i < numChildren; ++i)
+    {
+        int targetSize = (int)(targetChildSize * flexScales[i]);
+        if (remainder)
+        {
+            acc += add;
+            if (acc >= 0.5f)
+            {
+                acc -= 1.0f;
+                ++targetSize;
+                --remainder;
+            }
+        }
+        sizes[i] = Clamp(targetSize, minSizes[i], maxSizes[i]);
+    }
+
+    // Error correction passes
+    for (;;)
+    {
+        int actualTotalSize = 0;
+        for (unsigned i = 0; i < numChildren; ++i)
+            actualTotalSize += sizes[i];
+        int error = targetTotalSize - actualTotalSize;
+        // Break if no error
+        if (!error)
+            break;
+
+        // Check which of the children can be resized to correct the error. If none, must break
+        PODVector<unsigned> resizable;
+        for (unsigned i = 0; i < numChildren; ++i)
+        {
+            if (error < 0 && sizes[i] > minSizes[i])
+                resizable.Push(i);
+            else if (error > 0 && sizes[i] < maxSizes[i])
+                resizable.Push(i);
+        }
+        if (resizable.Empty())
+            break;
+
+        int numResizable = resizable.Size();
+        int errorPerChild = error / numResizable;
+        remainder = (abs(error)) % numResizable;
+        add = (float)remainder / numResizable;
+        acc = 0.0f;
+
+        for (int i = 0; i < numResizable; ++i)
+        {
+            unsigned index = resizable[i];
+            int targetSize = sizes[index] + errorPerChild;
+            if (remainder)
+            {
+                acc += add;
+                if (acc >= 0.5f)
+                {
+                    acc -= 1.0f;
+                    targetSize = error < 0 ? targetSize - 1 : targetSize + 1;
+                    --remainder;
+                }
+            }
+
+            sizes[index] = Clamp(targetSize, minSizes[index], maxSizes[index]);
+        }
+    }
+
+    // Calculate final positions and store the minimum child element size
+    layoutMinSize_ = M_MAX_INT;
+    layoutMaxSize_ = 0;
+    int position = begin;
+    for (unsigned i = 0; i < numChildren; ++i)
+    {
+        positions[i] = position;
+        position += sizes[i] + spacing;
+        if (sizes[i] < layoutMinSize_)
+            layoutMinSize_ = sizes[i];
+        if (sizes[i] > layoutMaxSize_)
+            layoutMaxSize_ = sizes[i];
+    }
+}
+
+IntVector2 UIElement::GetLayoutChildPosition(UIElement* child)
+{
+    IntVector2 ret(IntVector2::ZERO);
+
+    HorizontalAlignment ha = child->GetHorizontalAlignment();
+    switch (ha)
+    {
+    case HA_LEFT:
+        ret.x_ = layoutBorder_.left_;
+        break;
+
+    case HA_RIGHT:
+        ret.x_ = -layoutBorder_.right_;
+        break;
+
+    default:
+        break;
+    }
+
+    VerticalAlignment va = child->GetVerticalAlignment();
+    switch (va)
+    {
+    case VA_TOP:
+        ret.y_ = layoutBorder_.top_;
+        break;
+
+    case VA_BOTTOM:
+        ret.y_ = -layoutBorder_.bottom_;
+        break;
+
+    default:
+        break;
+    }
+
+    return ret;
+}
+
+void UIElement::Detach()
+{
+    parent_ = 0;
+    MarkDirty();
+}
+
+void UIElement::VerifyChildAlignment()
+{
+    for (Vector<SharedPtr<UIElement> >::ConstIterator i = children_.Begin(); i != children_.End(); ++i)
+    {
+        // Reapply child alignments. If they are illegal compared to layout, they will be set left/top as neded
+        (*i)->SetHorizontalAlignment((*i)->GetHorizontalAlignment());
+        (*i)->SetVerticalAlignment((*i)->GetVerticalAlignment());
+    }
+}
+
+void UIElement::HandlePostUpdate(StringHash eventType, VariantMap& eventData)
+{
+    using namespace PostUpdate;
+
+    UpdateAttributeAnimations(eventData[P_TIMESTEP].GetFloat());
+}
+
+}
+
+}

+ 722 - 0
Source/Atomic/UI/SystemUI/UIElement.h

@@ -0,0 +1,722 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "../../Math/Vector2.h"
+#include "../../Resource/XMLFile.h"
+#include "../../Scene/Animatable.h"
+#include "SystemUIBatch.h"
+
+namespace Atomic
+{
+
+class ResourceCache;
+
+namespace SystemUI
+{
+
+/// %UI element horizontal alignment.
+enum HorizontalAlignment
+{
+    HA_LEFT = 0,
+    HA_CENTER,
+    HA_RIGHT
+};
+
+/// %UI element vertical alignment.
+enum VerticalAlignment
+{
+    VA_TOP = 0,
+    VA_CENTER,
+    VA_BOTTOM
+};
+
+/// %UI element corners.
+enum Corner
+{
+    C_TOPLEFT = 0,
+    C_TOPRIGHT,
+    C_BOTTOMLEFT,
+    C_BOTTOMRIGHT,
+    MAX_UIELEMENT_CORNERS
+};
+
+/// %UI element orientation.
+enum Orientation
+{
+    O_HORIZONTAL = 0,
+    O_VERTICAL
+};
+
+/// %UI element focus mode.
+enum FocusMode
+{
+    /// Is not focusable and does not affect existing focus.
+    FM_NOTFOCUSABLE = 0,
+    /// Resets focus when clicked.
+    FM_RESETFOCUS,
+    /// Is focusable.
+    FM_FOCUSABLE,
+    /// Is focusable and also defocusable by pressing ESC.
+    FM_FOCUSABLE_DEFOCUSABLE
+};
+
+/// Layout operation mode.
+enum LayoutMode
+{
+    /// No layout operations will be performed.
+    LM_FREE = 0,
+    /// Layout child elements horizontally and resize them to fit. Resize element if necessary.
+    LM_HORIZONTAL,
+    /// Layout child elements vertically and resize them to fit. Resize element if necessary.
+    LM_VERTICAL
+};
+
+/// Traversal mode for rendering.
+enum TraversalMode
+{
+    /// Traverse thru children having same priority first and recurse into their children before traversing children having higher priority.
+    TM_BREADTH_FIRST = 0,
+    /// Traverse thru each child and its children immediately after in sequence.
+    TM_DEPTH_FIRST
+};
+
+/// Drag and drop disabled.
+static const unsigned DD_DISABLED = 0x0;
+/// Drag and drop source flag.
+static const unsigned DD_SOURCE = 0x1;
+/// Drag and drop target flag.
+static const unsigned DD_TARGET = 0x2;
+/// Drag and drop source and target.
+static const unsigned DD_SOURCE_AND_TARGET = 0x3;
+
+class Cursor;
+
+/// Base class for %UI elements.
+class ATOMIC_API UIElement : public Animatable
+{
+    OBJECT(UIElement);
+    BASEOBJECT(UIElement);
+
+public:
+    /// Construct.
+    UIElement(Context* context);
+    /// Destruct.
+    virtual ~UIElement();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Apply attribute changes that can not be applied immediately.
+    virtual void ApplyAttributes();
+    /// Load from XML data. Return true if successful.
+    virtual bool LoadXML(const XMLElement& source, bool setInstanceDefault = false);
+    /// Load from XML data with style. Return true if successful.
+    virtual bool LoadXML(const XMLElement& source, XMLFile* styleFile, bool setInstanceDefault = false);
+    /// Create a child by loading from XML data with style. Return true if successful.
+    virtual bool LoadChildXML(const XMLElement& childElem, XMLFile* styleFile = 0, bool setInstanceDefault = false);
+    /// Save as XML data. Return true if successful.
+    virtual bool SaveXML(XMLElement& dest) const;
+
+    /// Perform UI element update.
+    virtual void Update(float timeStep);
+    /// Return whether is visible and inside a scissor rectangle and should be rendered.
+    virtual bool IsWithinScissor(const IntRect& currentScissor);
+    /// Update and return screen position.
+    virtual const IntVector2& GetScreenPosition() const;
+    /// Return UI rendering batches.
+    virtual void GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor);
+    /// Return UI rendering batches for debug draw.
+    virtual void GetDebugDrawBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor);
+    /// React to mouse hover.
+    virtual void OnHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
+    /// React to mouse click begin.
+    virtual void OnClickBegin
+        (const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers, Cursor* cursor);
+    /// React to mouse click end.
+    virtual void OnClickEnd
+        (const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers, Cursor* cursor,
+            UIElement* beginElement);
+    /// React to double mouse click.
+    virtual void OnDoubleClick
+        (const IntVector2& position, const IntVector2& screenPosition, int button, int buttons, int qualifiers, Cursor* cursor);
+    /// React to mouse drag begin.
+    virtual void
+        OnDragBegin(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
+    /// React to mouse drag motion.
+    virtual void OnDragMove
+        (const IntVector2& position, const IntVector2& screenPosition, const IntVector2& deltaPos, int buttons, int qualifiers,
+            Cursor* cursor);
+    /// React to mouse drag end.
+    virtual void
+        OnDragEnd(const IntVector2& position, const IntVector2& screenPosition, int dragButtons, int releaseButton, Cursor* cursor);
+    /// React to a mouse drag cancel event (ie, when an extra button is pressed)
+    virtual void OnDragCancel
+        (const IntVector2& position, const IntVector2& screenPosition, int dragButtons, int cancelButton, Cursor* cursor);
+    /// React to drag and drop test. Return true to signal that the drop is acceptable.
+    virtual bool OnDragDropTest(UIElement* source);
+    /// React to drag and drop finish. Return true to signal that the drop was accepted.
+    virtual bool OnDragDropFinish(UIElement* source);
+    /// React to mouse wheel.
+    virtual void OnWheel(int delta, int buttons, int qualifiers);
+    /// React to a key press.
+    virtual void OnKey(int key, int buttons, int qualifiers);
+    /// React to text input event.
+    virtual void OnTextInput(const String& text, int buttons, int qualifiers);
+
+    /// React to resize.
+    virtual void OnResize() { }
+
+    /// React to position change.
+    virtual void OnPositionSet() { }
+
+    /// React to editable status change.
+    virtual void OnSetEditable() { }
+
+    /// React to indent change.
+    virtual void OnIndentSet() { }
+
+    /// Load from an XML file. Return true if successful.
+    bool LoadXML(Deserializer& source);
+    /// Save to an XML file. Return true if successful.
+    bool SaveXML(Serializer& dest, const String& indentation = "\t") const;
+    /// Filter attributes in serialization process.
+    bool FilterAttributes(XMLElement& dest) const;
+
+    /// Set name.
+    void SetName(const String& name);
+    /// Set position.
+    void SetPosition(const IntVector2& position);
+    /// Set position.
+    void SetPosition(int x, int y);
+    /// Set size.
+    void SetSize(const IntVector2& size);
+    /// Set size.
+    void SetSize(int width, int height);
+    /// Set width only.
+    void SetWidth(int width);
+    /// Set height only.
+    void SetHeight(int height);
+    /// Set minimum size.
+    void SetMinSize(const IntVector2& minSize);
+    /// Set minimum size.
+    void SetMinSize(int width, int height);
+    /// Set minimum width.
+    void SetMinWidth(int width);
+    /// Set minimum height.
+    void SetMinHeight(int height);
+    /// Set maximum size.
+    void SetMaxSize(const IntVector2& maxSize);
+    /// Set maximum size.
+    void SetMaxSize(int width, int height);
+    /// Set maximum width.
+    void SetMaxWidth(int width);
+    /// Set maximum height.
+    void SetMaxHeight(int height);
+    /// Set fixed size.
+    void SetFixedSize(const IntVector2& size);
+    /// Set fixed size.
+    void SetFixedSize(int width, int height);
+    /// Set fixed width.
+    void SetFixedWidth(int width);
+    /// Set fixed height.
+    void SetFixedHeight(int height);
+    /// Set horizontal and vertical alignment.
+    void SetAlignment(HorizontalAlignment hAlign, VerticalAlignment vAlign);
+    /// Set horizontal alignment.
+    void SetHorizontalAlignment(HorizontalAlignment align);
+    /// Set vertical alignment.
+    void SetVerticalAlignment(VerticalAlignment align);
+    /// Set child element clipping border.
+    void SetClipBorder(const IntRect& rect);
+    /// Set color on all corners.
+    void SetColor(const Color& color);
+    /// Set color on one corner.
+    void SetColor(Corner corner, const Color& color);
+    /// Set priority.
+    void SetPriority(int priority);
+    /// Set opacity.
+    void SetOpacity(float opacity);
+    /// Set whether should be brought to front when focused.
+    void SetBringToFront(bool enable);
+    /// Set whether should be put to background when another element is focused.
+    void SetBringToBack(bool enable);
+    /// Set whether should clip child elements. Default false.
+    void SetClipChildren(bool enable);
+    /// Set whether should sort child elements according to priority. Default true.
+    void SetSortChildren(bool enable);
+    /// Set whether parent elements' opacity affects opacity. Default true.
+    void SetUseDerivedOpacity(bool enable);
+    /// Set whether reacts to input. Default false, but is enabled by subclasses if applicable.
+    void SetEnabled(bool enable);
+    /// Set enabled state on self and child elements. Elements' own enabled state is remembered (IsEnabledSelf) and can be restored.
+    void SetDeepEnabled(bool enable);
+    /// Reset enabled state to the element's remembered state prior to calling SetDeepEnabled.
+    void ResetDeepEnabled();
+    /// Set enabled state on self and child elements. Unlike SetDeepEnabled this does not remember the elements' own enabled state, but overwrites it.
+    void SetEnabledRecursive(bool enable);
+    /// Set whether value is editable through input. Not applicable to all elements. Default true.
+    void SetEditable(bool enable);
+    /// Set whether is focused. Only one element can be focused at a time.
+    void SetFocus(bool enable);
+    /// Set selected mode. Actual meaning is element dependent, for example constant hover or pressed effect.
+    void SetSelected(bool enable);
+    /// Set whether is visible. Visibility propagates to child elements.
+    void SetVisible(bool enable);
+    /// Set focus mode.
+    void SetFocusMode(FocusMode mode);
+    /// Set drag and drop flags.
+    void SetDragDropMode(unsigned mode);
+    /// Set style from an XML file. Find the style element by name. If the style file is not explicitly provided, use the default style from parental chain. Return true if the style is applied successfully.
+    bool SetStyle(const String& styleName, XMLFile* file = 0);
+    /// Set style from an XML element. Return true if the style is applied successfully.
+    bool SetStyle(const XMLElement& element);
+    /// Set style from an XML file. Find the style element automatically. If the style file is not explicitly provided, use the default style from parental chain. Return true if the style is applied successfully.
+    bool SetStyleAuto(XMLFile* file = 0);
+    /// Set default style file for later use by children elements.
+    void SetDefaultStyle(XMLFile* style);
+    /// Set layout.
+    void SetLayout(LayoutMode mode, int spacing = 0, const IntRect& border = IntRect::ZERO);
+    /// Set layout mode only.
+    void SetLayoutMode(LayoutMode mode);
+    /// Set layout spacing.
+    void SetLayoutSpacing(int spacing);
+    /// Set layout border.
+    void SetLayoutBorder(const IntRect& border);
+    /// Set layout flex scale.
+    void SetLayoutFlexScale(const Vector2& scale);
+    /// Set horizontal indentation.
+    void SetIndent(int indent);
+    /// Set indent spacing (number of pixels per indentation level).
+    void SetIndentSpacing(int indentSpacing);
+    /// Manually update layout. Should not be necessary in most cases, but is provided for completeness.
+    void UpdateLayout();
+    /// Disable automatic layout update. Should only be used if there are performance problems.
+    void DisableLayoutUpdate();
+    /// Enable automatic layout update.
+    void EnableLayoutUpdate();
+    /// Bring UI element to front.
+    void BringToFront();
+    /// Create and add a child element and return it.
+    UIElement* CreateChild(StringHash type, const String& name = String::EMPTY, unsigned index = M_MAX_UNSIGNED);
+    /// Add a child element.
+    void AddChild(UIElement* element);
+    /// Insert a child element into a specific position in the child list.
+    void InsertChild(unsigned index, UIElement* element);
+    /// Remove a child element. Starting search at specified index if provided.
+    void RemoveChild(UIElement* element, unsigned index = 0);
+    /// Remove a child element at index.
+    void RemoveChildAtIndex(unsigned index);
+    /// Remove all child elements.
+    void RemoveAllChildren();
+    /// Remove from the parent element. If no other shared pointer references exist, causes immediate deletion.
+    void Remove();
+    /// Find child index. Return M_MAX_UNSIGNED if not found.
+    unsigned FindChild(UIElement* element) const;
+    /// Set parent element. Same as parent->InsertChild(index, this).
+    void SetParent(UIElement* parent, unsigned index = M_MAX_UNSIGNED);
+    /// Set a user variable.
+    void SetVar(StringHash key, const Variant& value);
+    /// Mark as internally (programmatically) created. Used when an element composes itself out of child elements.
+    void SetInternal(bool enable);
+    /// Set traversal mode for rendering. The default traversal mode is TM_BREADTH_FIRST for non-root element. Root element should be set to TM_DEPTH_FIRST to avoid artifacts during rendering.
+    void SetTraversalMode(TraversalMode traversalMode);
+    /// Set element event sender flag. When child element is added or deleted, the event would be sent using UIElement found in the parental chain having this flag set. If not set, the event is sent using UI's root as per normal.
+    void SetElementEventSender(bool flag);
+
+    /// Template version of creating a child element.
+    template <class T> T* CreateChild(const String& name = String::EMPTY, unsigned index = M_MAX_UNSIGNED);
+
+    /// Return name.
+    const String& GetName() const { return name_; }
+
+    /// Return position.
+    const IntVector2& GetPosition() const { return position_; }
+
+    /// Return size.
+    const IntVector2& GetSize() const { return size_; }
+
+    /// Return width.
+    int GetWidth() const { return size_.x_; }
+
+    /// Return height.
+    int GetHeight() const { return size_.y_; }
+
+    /// Return minimum size.
+    const IntVector2& GetMinSize() const { return minSize_; }
+
+    /// Return minimum width.
+    int GetMinWidth() const { return minSize_.x_; }
+
+    /// Return minimum height.
+    int GetMinHeight() const { return minSize_.y_; }
+
+    /// Return maximum size.
+    const IntVector2& GetMaxSize() const { return maxSize_; }
+
+    /// Return minimum width.
+    int GetMaxWidth() const { return maxSize_.x_; }
+
+    /// Return minimum height.
+    int GetMaxHeight() const { return maxSize_.y_; }
+
+    /// Return true if size is fixed.
+    bool IsFixedSize() const { return minSize_ == maxSize_; }
+
+    /// Return true if width is fixed.
+    bool IsFixedWidth() const { return minSize_.x_ == maxSize_.x_; }
+
+    /// Return true if height is fixed.
+    bool IsFixedHeight() const { return minSize_.y_ == maxSize_.y_; }
+
+    /// Return child element offset.
+    const IntVector2& GetChildOffset() const { return childOffset_; }
+
+    /// Return horizontal alignment.
+    HorizontalAlignment GetHorizontalAlignment() const { return horizontalAlignment_; }
+
+    /// Return vertical alignment.
+    VerticalAlignment GetVerticalAlignment() const { return verticalAlignment_; }
+
+    /// Return child element clipping border.
+    const IntRect& GetClipBorder() const { return clipBorder_; }
+
+    /// Return corner color.
+    const Color& GetColor(Corner corner) const { return color_[corner]; }
+
+    /// Return priority.
+    int GetPriority() const { return priority_; }
+
+    /// Return opacity.
+    float GetOpacity() const { return opacity_; }
+
+    /// Return derived opacity (affected by parent elements.) If UseDerivedOpacity is false, returns same as element's own opacity.
+    float GetDerivedOpacity() const;
+
+    /// Return whether should be brought to front when focused.
+    bool GetBringToFront() const { return bringToFront_; }
+
+    /// Return whether should be put to background when another element is focused.
+    bool GetBringToBack() const { return bringToBack_; }
+
+    /// Return whether should clip child elements.
+    bool GetClipChildren() const { return clipChildren_; }
+
+    /// Return whether should sort child elements according to priority.
+    bool GetSortChildren() const { return sortChildren_; }
+
+    /// Return whether parent elements' opacity affects opacity.
+    bool GetUseDerivedOpacity() const { return useDerivedOpacity_; }
+
+    /// Return whether has focus.
+    bool HasFocus() const;
+
+    /// Return whether reacts to input.
+    bool IsEnabled() const { return enabled_; }
+
+    /// Returns the element's last own enabled state. May be different than the value returned by IsEnabled when SetDeepEnabled has been used.
+    bool IsEnabledSelf() const { return enabledPrev_; }
+
+    /// Return whether value is editable through input.
+    bool IsEditable() const { return editable_; }
+
+    /// Return whether is selected. Actual meaning is element dependent.
+    bool IsSelected() const { return selected_; }
+
+    /// Return whether element itself should be visible. Elements can be also hidden due to the parent being not visible, use IsVisibleEffective() to check.
+    bool IsVisible() const { return visible_; }
+    
+    /// Return whether element is effectively visible (parent element chain is visible.)
+    bool IsVisibleEffective() const;
+
+    /// Return whether the cursor is hovering on this element.
+    bool IsHovering() const { return hovering_; }
+
+    /// Return whether is internally created.
+    bool IsInternal() const { return internal_; }
+
+    /// Return whether has different color in at least one corner.
+    bool HasColorGradient() const { return colorGradient_; }
+
+    /// Return focus mode.
+    FocusMode GetFocusMode() const { return focusMode_; }
+
+    /// Return drag and drop flags.
+    unsigned GetDragDropMode() const { return dragDropMode_; }
+
+    /// Return applied style name. Return an empty string when the applied style is an 'auto' style (i.e. style derived from instance's type).
+    const String& GetAppliedStyle() const;
+    /// Return default style.
+    XMLFile* GetDefaultStyle(bool recursiveUp = true) const;
+
+    /// Return layout mode.
+    LayoutMode GetLayoutMode() const { return layoutMode_; }
+
+    /// Return layout spacing.
+    int GetLayoutSpacing() const { return layoutSpacing_; }
+
+    /// Return layout border.
+    const IntRect& GetLayoutBorder() const { return layoutBorder_; }
+
+    /// Return layout flex scale.
+    const Vector2& GetLayoutFlexScale() const { return layoutFlexScale_; }
+
+    /// Return number of child elements.
+    unsigned GetNumChildren(bool recursive = false) const;
+    /// Return child element by index.
+    UIElement* GetChild(unsigned index) const;
+    /// Return child element by name.
+    UIElement* GetChild(const String& name, bool recursive = false) const;
+    /// Return child element by variable. If only key is provided, return the first child having the matching variable key. If value is also provided then the actual variable value would also be checked against.
+    UIElement* GetChild(const StringHash& key, const Variant& value = Variant::EMPTY, bool recursive = false) const;
+
+    /// Return immediate child elements.
+    const Vector<SharedPtr<UIElement> >& GetChildren() const { return children_; }
+
+    /// Return child elements either recursively or non-recursively.
+    void GetChildren(PODVector<UIElement*>& dest, bool recursive = false) const;
+
+    /// Return parent element.
+    UIElement* GetParent() const { return parent_; }
+
+    /// Return root element.
+    UIElement* GetRoot() const;
+    /// Return derived color. Only valid when no gradient.
+    const Color& GetDerivedColor() const;
+    /// Return a user variable.
+    const Variant& GetVar(const StringHash& key) const;
+
+    /// Return all user variables.
+    const VariantMap& GetVars() const { return vars_; }
+
+    /// Return the drag button combo if this element is being dragged.
+    int GetDragButtonCombo() const { return dragButtonCombo_; }
+
+    /// Return the number of buttons dragging this element.
+    unsigned GetDragButtonCount() const { return dragButtonCount_; }
+
+    /// Convert screen coordinates to element coordinates.
+    IntVector2 ScreenToElement(const IntVector2& screenPosition);
+    /// Convert element coordinates to screen coordinates.
+    IntVector2 ElementToScreen(const IntVector2& position);
+    /// Return whether a point (either in element or screen coordinates) is inside the element.
+    bool IsInside(IntVector2 position, bool isScreen);
+    /// Return whether a point (either in element or screen coordinates) is inside the combined rect of the element and its children.
+    bool IsInsideCombined(IntVector2 position, bool isScreen);
+    /// Return combined screen coordinate rect of element and its children.
+    IntRect GetCombinedScreenRect();
+    /// Sort child elements if sorting enabled and order dirty. Called by UI.
+    void SortChildren();
+
+    /// Return minimum layout element size in the layout direction. Only valid after layout has been calculated. Used internally by UI for optimizations.
+    int GetLayoutMinSize() const { return layoutMinSize_; }
+
+    /// Return maximum layout element size in the layout direction. Only valid after layout has been calculated. Used internally by UI for optimizations.
+    int GetLayoutMaxSize() const { return layoutMaxSize_; }
+
+    /// Return horizontal indentation.
+    int GetIndent() const { return indent_; }
+
+    /// Return indent spacing (number of pixels per indentation level).
+    int GetIndentSpacing() const { return indentSpacing_; }
+
+    /// Return indent width in pixels.
+    int GetIndentWidth() const { return indent_ * indentSpacing_; }
+
+    /// Set child offset.
+    void SetChildOffset(const IntVector2& offset);
+    /// Set hovering state.
+    void SetHovering(bool enable);
+    /// Adjust scissor for rendering.
+    void AdjustScissor(IntRect& currentScissor);
+    /// Get UI rendering batches with a specified offset. Also recurse to child elements.
+    void
+        GetBatchesWithOffset(IntVector2& offset, PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, IntRect currentScissor);
+
+    /// Return color attribute. Uses just the top-left color.
+    const Color& GetColorAttr() const { return color_[0]; }
+
+    /// Return traversal mode for rendering.
+    TraversalMode GetTraversalMode() const { return traversalMode_; }
+
+    /// Return whether element should send child added / removed events by itself. If false, defers to parent element.
+    bool IsElementEventSender() const { return elementEventSender_; }
+
+    /// Get element which should send child added / removed events.
+    UIElement* GetElementEventSender() const;
+
+protected:
+    /// Handle attribute animation added.
+    virtual void OnAttributeAnimationAdded();
+    /// Handle attribute animation removed.
+    virtual void OnAttributeAnimationRemoved();
+    /// Set object attribute animation internal.
+    virtual void
+        SetObjectAttributeAnimation(const String& name, ValueAnimation* attributeAnimation, WrapMode wrapMode, float speed);
+    /// Mark screen position as needing an update.
+    void MarkDirty();
+    /// Remove child XML element by matching attribute name.
+    bool RemoveChildXML(XMLElement& parent, const String& name) const;
+    /// Remove child XML element by matching attribute name and value.
+    bool RemoveChildXML(XMLElement& parent, const String& name, const String& value) const;
+    /// Filter UI-style attributes in serialization process.
+    bool FilterUIStyleAttributes(XMLElement& dest, const XMLElement& styleElem) const;
+    /// Filter implicit attributes in serialization process.
+    virtual bool FilterImplicitAttributes(XMLElement& dest) const;
+
+    /// Name.
+    String name_;
+    /// Child elements.
+    Vector<SharedPtr<UIElement> > children_;
+    /// Parent element.
+    UIElement* parent_;
+    /// Child element clipping border.
+    IntRect clipBorder_;
+    /// Colors.
+    Color color_[MAX_UIELEMENT_CORNERS];
+    /// User variables.
+    VariantMap vars_;
+    /// Priority.
+    int priority_;
+    /// Bring to front when focused flag.
+    bool bringToFront_;
+    /// Bring to back when defocused flag.
+    bool bringToBack_;
+    /// Clip children flag.
+    bool clipChildren_;
+    /// Sort children according to priority flag.
+    bool sortChildren_;
+    /// Use derived opacity flag.
+    bool useDerivedOpacity_;
+    /// Input enabled flag.
+    bool enabled_;
+    /// Last SetEnabled flag before any SetDeepEnabled.
+    bool enabledPrev_;
+    /// Value editable flag.
+    bool editable_;
+    /// Selected flag.
+    bool selected_;
+    /// Visible flag.
+    bool visible_;
+    /// Hovering flag.
+    bool hovering_;
+    /// Internally created flag.
+    bool internal_;
+    /// Focus mode.
+    FocusMode focusMode_;
+    /// Drag and drop flags.
+    unsigned dragDropMode_;
+    /// Layout mode.
+    LayoutMode layoutMode_;
+    /// Layout spacing.
+    int layoutSpacing_;
+    /// Layout borders.
+    IntRect layoutBorder_;
+    /// Layout flex scale.
+    Vector2 layoutFlexScale_;
+    /// Resize nesting level to prevent multiple events and endless loop.
+    unsigned resizeNestingLevel_;
+    /// Layout update nesting level to prevent endless loop.
+    unsigned layoutNestingLevel_;
+    /// Layout element minimum size in layout direction.
+    int layoutMinSize_;
+    /// Layout element maximum size in layout direction.
+    int layoutMaxSize_;
+    /// Horizontal indentation.
+    int indent_;
+    /// Indent spacing (number of pixels per indentation level).
+    int indentSpacing_;
+    /// Position.
+    IntVector2 position_;
+    /// Screen position.
+    mutable IntVector2 screenPosition_;
+    /// Screen position dirty flag.
+    mutable bool positionDirty_;
+    /// Applied style.
+    String appliedStyle_;
+    /// Drag button combo.
+    int dragButtonCombo_;
+    /// Drag button count.
+    unsigned dragButtonCount_;
+
+private:
+    /// Return child elements recursively.
+    void GetChildrenRecursive(PODVector<UIElement*>& dest) const;
+    /// Calculate layout width for resizing the parent element.
+    int CalculateLayoutParentSize(const PODVector<int>& sizes, int begin, int end, int spacing);
+    /// Calculate child widths/positions in the layout.
+    void CalculateLayout
+        (PODVector<int>& positions, PODVector<int>& sizes, const PODVector<int>& minSizes, const PODVector<int>& maxSizes,
+            const PODVector<float>& flexScales, int targetWidth, int begin, int end, int spacing);
+    /// Get child element constant position in a layout.
+    IntVector2 GetLayoutChildPosition(UIElement* child);
+    /// Detach from parent.
+    void Detach();
+    /// Verify that child elements have proper alignment for layout mode.
+    void VerifyChildAlignment();
+    /// Handle logic post-update event.
+    void HandlePostUpdate(StringHash eventType, VariantMap& eventData);
+
+    /// Size.
+    IntVector2 size_;
+    /// Minimum size.
+    IntVector2 minSize_;
+    /// Maximum size.
+    IntVector2 maxSize_;
+    /// Child elements' offset. Used internally.
+    IntVector2 childOffset_;
+    /// Horizontal alignment.
+    HorizontalAlignment horizontalAlignment_;
+    /// Vertical alignment.
+    VerticalAlignment verticalAlignment_;
+    /// Opacity.
+    float opacity_;
+    /// Derived opacity.
+    mutable float derivedOpacity_;
+    /// Derived color. Only valid when no gradient.
+    mutable Color derivedColor_;
+    /// Derived opacity dirty flag.
+    mutable bool opacityDirty_;
+    /// Derived color dirty flag (only used when no gradient.)
+    mutable bool derivedColorDirty_;
+    /// Child priority sorting dirty flag.
+    bool sortOrderDirty_;
+    /// Has color gradient flag.
+    bool colorGradient_;
+    /// Default style file.
+    SharedPtr<XMLFile> defaultStyle_;
+    /// Traversal mode for rendering.
+    TraversalMode traversalMode_;
+    /// Flag whether node should send child added / removed events by itself.
+    bool elementEventSender_;
+    /// XPath query for selecting UI-style.
+    static XPathQuery styleXPathQuery_;
+};
+
+template <class T> T* UIElement::CreateChild(const String& name, unsigned index)
+{
+    return static_cast<T*>(CreateChild(T::GetTypeStatic(), name, index));
+}
+
+}
+
+}

+ 427 - 0
Source/Atomic/UI/SystemUI/Window.cpp

@@ -0,0 +1,427 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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 "../../Core/Context.h"
+#include "../../Input/InputEvents.h"
+#include "Cursor.h"
+#include "SystemUI.h"
+#include "SystemUIEvents.h"
+#include "Window.h"
+
+#include "../../DebugNew.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+static const int DEFAULT_RESIZE_BORDER = 4;
+
+extern const char* UI_CATEGORY;
+
+Window::Window(Context* context) :
+    BorderImage(context),
+    movable_(false),
+    resizable_(false),
+    fixedWidthResizing_(false),
+    fixedHeightResizing_(false),
+    resizeBorder_(DEFAULT_RESIZE_BORDER, DEFAULT_RESIZE_BORDER, DEFAULT_RESIZE_BORDER, DEFAULT_RESIZE_BORDER),
+    dragMode_(DRAG_NONE),
+    modal_(false),
+    modalAutoDismiss_(true),
+    modalShadeColor_(Color::TRANSPARENT),
+    modalFrameColor_(Color::TRANSPARENT),
+    modalFrameSize_(IntVector2::ZERO)
+{
+    bringToFront_ = true;
+    clipChildren_ = true;
+    SetEnabled(true);
+}
+
+Window::~Window()
+{
+}
+
+void Window::RegisterObject(Context* context)
+{
+    context->RegisterFactory<Window>(UI_CATEGORY);
+
+    COPY_BASE_ATTRIBUTES(BorderImage);
+    UPDATE_ATTRIBUTE_DEFAULT_VALUE("Bring To Front", true);
+    UPDATE_ATTRIBUTE_DEFAULT_VALUE("Clip Children", true);
+    UPDATE_ATTRIBUTE_DEFAULT_VALUE("Is Enabled", true);
+    ACCESSOR_ATTRIBUTE("Resize Border", GetResizeBorder, SetResizeBorder, IntRect, IntRect(DEFAULT_RESIZE_BORDER, \
+        DEFAULT_RESIZE_BORDER, DEFAULT_RESIZE_BORDER, DEFAULT_RESIZE_BORDER), AM_FILE);
+    ACCESSOR_ATTRIBUTE("Is Movable", IsMovable, SetMovable, bool, false, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Is Resizable", IsResizable, SetResizable, bool, false, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Fixed Width Resizing", GetFixedWidthResizing, SetFixedWidthResizing, bool, false, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Fixed Height Resizing", GetFixedHeightResizing, SetFixedHeightResizing, bool, false, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Is Modal", IsModal, SetModal, bool, false, AM_FILE | AM_NOEDIT);
+    ACCESSOR_ATTRIBUTE("Modal Shade Color", GetModalShadeColor, SetModalShadeColor, Color, Color::TRANSPARENT, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Modal Frame Color", GetModalFrameColor, SetModalFrameColor, Color, Color::TRANSPARENT, AM_FILE);
+    ACCESSOR_ATTRIBUTE("Modal Frame Size", GetModalFrameSize, SetModalFrameSize, IntVector2, IntVector2::ZERO, AM_FILE);
+    // Modal auto dismiss is purposefully not an attribute, as using it can make the editor lock up.
+    // Instead it should be set false in code when needed
+}
+
+void Window::GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor)
+{
+    if (modal_)
+    {
+        // Modal shade
+        if (modalShadeColor_ != Color::TRANSPARENT)
+        {
+            UIElement* rootElement = GetRoot();
+            const IntVector2& rootSize = rootElement->GetSize();
+            SystemUIBatch batch(rootElement, BLEND_ALPHA, IntRect(0, 0, rootSize.x_, rootSize.y_), 0, &vertexData);
+            batch.SetColor(modalShadeColor_);
+            batch.AddQuad(0, 0, rootSize.x_, rootSize.y_, 0, 0);
+            SystemUIBatch::AddOrMerge(batch, batches);
+        }
+
+        // Modal frame
+        if (modalFrameColor_ != Color::TRANSPARENT && modalFrameSize_ != IntVector2::ZERO)
+        {
+            SystemUIBatch batch(this, BLEND_ALPHA, currentScissor, 0, &vertexData);
+            int x = GetIndentWidth();
+            IntVector2 size = GetSize();
+            size.x_ -= x;
+            batch.SetColor(modalFrameColor_);
+            batch.AddQuad(x - modalFrameSize_.x_, -modalFrameSize_.y_, size.x_ + 2 * modalFrameSize_.x_,
+                size.y_ + 2 * modalFrameSize_.y_, 0, 0);
+            SystemUIBatch::AddOrMerge(batch, batches);
+        }
+    }
+
+    BorderImage::GetBatches(batches, vertexData, currentScissor);
+}
+
+void Window::OnHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
+{
+    UIElement::OnHover(position, screenPosition, buttons, qualifiers, cursor);
+
+    if (dragMode_ == DRAG_NONE)
+    {
+        WindowDragMode mode = GetDragMode(position);
+        SetCursorShape(mode, cursor);
+    }
+    else
+        SetCursorShape(dragMode_, cursor);
+}
+
+void Window::OnDragBegin(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
+{
+    UIElement::OnDragBegin(position, screenPosition, buttons, qualifiers, cursor);
+
+    if (buttons != MOUSEB_LEFT || !CheckAlignment())
+    {
+        dragMode_ = DRAG_NONE;
+        return;
+    }
+
+    dragBeginCursor_ = screenPosition;
+    dragBeginPosition_ = GetPosition();
+    dragBeginSize_ = GetSize();
+    dragMode_ = GetDragMode(position);
+    SetCursorShape(dragMode_, cursor);
+}
+
+void Window::OnDragMove(const IntVector2& position, const IntVector2& screenPosition, const IntVector2& deltaPos, int buttons,
+    int qualifiers, Cursor* cursor)
+{
+    if (dragMode_ == DRAG_NONE)
+        return;
+
+    IntVector2 delta = screenPosition - dragBeginCursor_;
+    IntVector2 dragSize;
+    IntVector2 resizeBorderSize(resizeBorder_.left_ + resizeBorder_.right_, resizeBorder_.top_ + resizeBorder_.bottom_);
+
+    const IntVector2& position_ = GetPosition();
+    const IntVector2& size_ = GetSize();
+    const IntVector2& minSize_ = GetMinSize();
+    const IntVector2& maxSize_ = GetMaxSize();
+
+    switch (dragMode_)
+    {
+    case DRAG_MOVE:
+        SetPosition(dragBeginPosition_ + delta);
+        break;
+
+    case DRAG_RESIZE_TOPLEFT:
+        SetPosition(Clamp(dragBeginPosition_.x_ + delta.x_, position_.x_ - (maxSize_.x_ - size_.x_),
+            position_.x_ + (size_.x_ - minSize_.x_)),
+            Clamp(dragBeginPosition_.y_ + delta.y_, position_.y_ - (maxSize_.y_ - size_.y_),
+                position_.y_ + (size_.y_ - minSize_.y_)));
+        dragSize = dragBeginSize_ - delta;
+        fixedWidthResizing_ ? SetFixedWidth(Max(dragSize.x_, resizeBorderSize.x_)) : SetWidth(dragSize.x_);
+        fixedHeightResizing_ ? SetFixedHeight(Max(dragSize.y_, resizeBorderSize.y_)) : SetHeight(dragSize.y_);
+        break;
+
+    case DRAG_RESIZE_TOP:
+        SetPosition(dragBeginPosition_.x_, Clamp(dragBeginPosition_.y_ + delta.y_, position_.y_ - (maxSize_.y_ - size_.y_),
+            position_.y_ + (size_.y_ - minSize_.y_)));
+        dragSize = IntVector2(dragBeginSize_.x_, dragBeginSize_.y_ - delta.y_);
+        fixedHeightResizing_ ? SetFixedHeight(Max(dragSize.y_, resizeBorderSize.y_)) : SetHeight(dragSize.y_);
+        break;
+
+    case DRAG_RESIZE_TOPRIGHT:
+        SetPosition(dragBeginPosition_.x_, Clamp(dragBeginPosition_.y_ + delta.y_, position_.y_ - (maxSize_.y_ - size_.y_),
+            position_.y_ + (size_.y_ - minSize_.y_)));
+        dragSize = IntVector2(dragBeginSize_.x_ + delta.x_, dragBeginSize_.y_ - delta.y_);
+        fixedWidthResizing_ ? SetFixedWidth(Max(dragSize.x_, resizeBorderSize.x_)) : SetWidth(dragSize.x_);
+        fixedHeightResizing_ ? SetFixedHeight(Max(dragSize.y_, resizeBorderSize.y_)) : SetHeight(dragSize.y_);
+        break;
+
+    case DRAG_RESIZE_RIGHT:
+        dragSize = IntVector2(dragBeginSize_.x_ + delta.x_, dragBeginSize_.y_);
+        fixedWidthResizing_ ? SetFixedWidth(Max(dragSize.x_, resizeBorderSize.x_)) : SetWidth(dragSize.x_);
+        break;
+
+    case DRAG_RESIZE_BOTTOMRIGHT:
+        dragSize = dragBeginSize_ + delta;
+        fixedWidthResizing_ ? SetFixedWidth(Max(dragSize.x_, resizeBorderSize.x_)) : SetWidth(dragSize.x_);
+        fixedHeightResizing_ ? SetFixedHeight(Max(dragSize.y_, resizeBorderSize.y_)) : SetHeight(dragSize.y_);
+        break;
+
+    case DRAG_RESIZE_BOTTOM:
+        dragSize = IntVector2(dragBeginSize_.x_, dragBeginSize_.y_ + delta.y_);
+        fixedHeightResizing_ ? SetFixedHeight(Max(dragSize.y_, resizeBorderSize.y_)) : SetHeight(dragSize.y_);
+        break;
+
+    case DRAG_RESIZE_BOTTOMLEFT:
+        SetPosition(Clamp(dragBeginPosition_.x_ + delta.x_, position_.x_ - (maxSize_.x_ - size_.x_),
+            position_.x_ + (size_.x_ - minSize_.x_)), dragBeginPosition_.y_);
+        dragSize = IntVector2(dragBeginSize_.x_ - delta.x_, dragBeginSize_.y_ + delta.y_);
+        fixedWidthResizing_ ? SetFixedWidth(Max(dragSize.x_, resizeBorderSize.x_)) : SetWidth(dragSize.x_);
+        fixedHeightResizing_ ? SetFixedHeight(Max(dragSize.y_, resizeBorderSize.y_)) : SetHeight(dragSize.y_);
+        break;
+
+    case DRAG_RESIZE_LEFT:
+        SetPosition(Clamp(dragBeginPosition_.x_ + delta.x_, position_.x_ - (maxSize_.x_ - size_.x_),
+            position_.x_ + (size_.x_ - minSize_.x_)), dragBeginPosition_.y_);
+        dragSize = IntVector2(dragBeginSize_.x_ - delta.x_, dragBeginSize_.y_);
+        fixedWidthResizing_ ? SetFixedWidth(Max(dragSize.x_, resizeBorderSize.x_)) : SetWidth(dragSize.x_);
+        break;
+
+    default:
+        break;
+    }
+
+    ValidatePosition();
+    SetCursorShape(dragMode_, cursor);
+}
+
+void Window::OnDragEnd(const IntVector2& position, const IntVector2& screenPosition, int dragButtons, int buttons, Cursor* cursor)
+{
+    UIElement::OnDragEnd(position, screenPosition, dragButtons, buttons, cursor);
+
+    dragMode_ = DRAG_NONE;
+}
+
+void Window::OnDragCancel(const IntVector2& position, const IntVector2& screenPosition, int dragButtons, int buttons,
+    Cursor* cursor)
+{
+    UIElement::OnDragCancel(position, screenPosition, dragButtons, buttons, cursor);
+
+    if (dragButtons == MOUSEB_LEFT)
+    {
+        dragMode_ = DRAG_NONE;
+        SetPosition(dragBeginPosition_);
+        SetSize(dragBeginSize_);
+    }
+}
+
+void Window::SetMovable(bool enable)
+{
+    movable_ = enable;
+}
+
+void Window::SetResizable(bool enable)
+{
+    resizable_ = enable;
+}
+
+void Window::SetFixedWidthResizing(bool enable)
+{
+    fixedWidthResizing_ = enable;
+}
+
+void Window::SetFixedHeightResizing(bool enable)
+{
+    fixedHeightResizing_ = enable;
+}
+
+void Window::SetResizeBorder(const IntRect& rect)
+{
+    resizeBorder_.left_ = Max(rect.left_, 0);
+    resizeBorder_.top_ = Max(rect.top_, 0);
+    resizeBorder_.right_ = Max(rect.right_, 0);
+    resizeBorder_.bottom_ = Max(rect.bottom_, 0);
+}
+
+void Window::SetModal(bool modal)
+{
+    if (GetSubsystem<SystemUI>()->SetModalElement(this, modal))
+    {
+        modal_ = modal;
+
+        using namespace ModalChanged;
+
+        VariantMap& eventData = GetEventDataMap();
+        eventData[P_ELEMENT] = this;
+        eventData[P_MODAL] = modal;
+        SendEvent(E_MODALCHANGED, eventData);
+    }
+}
+
+void Window::SetModalShadeColor(const Color& color)
+{
+    modalShadeColor_ = color;
+}
+
+void Window::SetModalFrameColor(const Color& color)
+{
+    modalFrameColor_ = color;
+}
+
+void Window::SetModalFrameSize(const IntVector2& size)
+{
+    modalFrameSize_ = size;
+}
+
+void Window::SetModalAutoDismiss(bool enable)
+{
+    modalAutoDismiss_ = enable;
+}
+
+WindowDragMode Window::GetDragMode(const IntVector2& position) const
+{
+    WindowDragMode mode = DRAG_NONE;
+
+    // Top row
+    if (position.y_ < resizeBorder_.top_)
+    {
+        if (movable_)
+            mode = DRAG_MOVE;
+        if (resizable_)
+        {
+            mode = DRAG_RESIZE_TOP;
+            if (position.x_ < resizeBorder_.left_)
+                mode = DRAG_RESIZE_TOPLEFT;
+            if (position.x_ >= GetWidth() - resizeBorder_.right_)
+                mode = DRAG_RESIZE_TOPRIGHT;
+        }
+    }
+    // Bottom row
+    else if (position.y_ >= GetHeight() - resizeBorder_.bottom_)
+    {
+        if (movable_)
+            mode = DRAG_MOVE;
+        if (resizable_)
+        {
+            mode = DRAG_RESIZE_BOTTOM;
+            if (position.x_ < resizeBorder_.left_)
+                mode = DRAG_RESIZE_BOTTOMLEFT;
+            if (position.x_ >= GetWidth() - resizeBorder_.right_)
+                mode = DRAG_RESIZE_BOTTOMRIGHT;
+        }
+    }
+    // Middle
+    else
+    {
+        if (movable_)
+            mode = DRAG_MOVE;
+        if (resizable_)
+        {
+            if (position.x_ < resizeBorder_.left_)
+                mode = DRAG_RESIZE_LEFT;
+            if (position.x_ >= GetWidth() - resizeBorder_.right_)
+                mode = DRAG_RESIZE_RIGHT;
+        }
+    }
+
+    return mode;
+}
+
+void Window::SetCursorShape(WindowDragMode mode, Cursor* cursor) const
+{
+    CursorShape shape = CS_NORMAL;
+
+    switch (mode)
+    {
+    case DRAG_RESIZE_TOP:
+    case DRAG_RESIZE_BOTTOM:
+        shape = CS_RESIZEVERTICAL;
+        break;
+
+    case DRAG_RESIZE_LEFT:
+    case DRAG_RESIZE_RIGHT:
+        shape = CS_RESIZEHORIZONTAL;
+        break;
+
+    case DRAG_RESIZE_TOPRIGHT:
+    case DRAG_RESIZE_BOTTOMLEFT:
+        shape = CS_RESIZEDIAGONAL_TOPRIGHT;
+        break;
+
+    case DRAG_RESIZE_TOPLEFT:
+    case DRAG_RESIZE_BOTTOMRIGHT:
+        shape = CS_RESIZEDIAGONAL_TOPLEFT;
+        break;
+
+    default:
+        break;
+    }
+
+    if (cursor)
+        cursor->SetShape(shape);
+}
+
+void Window::ValidatePosition()
+{
+    // Check that window does not go more than halfway outside its parent in either dimension
+    if (!parent_)
+        return;
+
+    const IntVector2& parentSize = parent_->GetSize();
+    IntVector2 position = GetPosition();
+    IntVector2 halfSize = GetSize() / 2;
+
+    position.x_ = Clamp(position.x_, -halfSize.x_, parentSize.x_ - halfSize.x_);
+    position.y_ = Clamp(position.y_, -halfSize.y_, parentSize.y_ - halfSize.y_);
+
+    SetPosition(position);
+}
+
+bool Window::CheckAlignment() const
+{
+    // Only top left-alignment is supported for move and resize
+    if (GetHorizontalAlignment() == HA_LEFT && GetVerticalAlignment() == VA_TOP)
+        return true;
+    else
+        return false;
+}
+
+}
+
+}

+ 173 - 0
Source/Atomic/UI/SystemUI/Window.h

@@ -0,0 +1,173 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "BorderImage.h"
+
+namespace Atomic
+{
+
+namespace SystemUI
+{
+
+/// %Window movement and resizing modes.
+enum WindowDragMode
+{
+    DRAG_NONE,
+    DRAG_MOVE,
+    DRAG_RESIZE_TOPLEFT,
+    DRAG_RESIZE_TOP,
+    DRAG_RESIZE_TOPRIGHT,
+    DRAG_RESIZE_RIGHT,
+    DRAG_RESIZE_BOTTOMRIGHT,
+    DRAG_RESIZE_BOTTOM,
+    DRAG_RESIZE_BOTTOMLEFT,
+    DRAG_RESIZE_LEFT
+};
+
+/// %Window %UI element that can optionally by moved or resized.
+class ATOMIC_API Window : public BorderImage
+{
+    OBJECT(Window);
+
+public:
+    /// Construct.
+    Window(Context* context);
+    /// Destruct.
+    virtual ~Window();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Return UI rendering batches.
+    virtual void GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor);
+
+    /// React to mouse hover.
+    virtual void OnHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
+    /// React to mouse drag begin.
+    virtual void
+        OnDragBegin(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
+    /// React to mouse drag motion.
+    virtual void OnDragMove
+        (const IntVector2& position, const IntVector2& screenPosition, const IntVector2& deltaPos, int buttons, int qualifiers,
+            Cursor* cursor);
+    /// React to mouse drag end.
+    virtual void
+        OnDragEnd(const IntVector2& position, const IntVector2& screenPosition, int dragButtons, int buttons, Cursor* cursor);
+    /// React to mouse drag cancel.
+    virtual void
+        OnDragCancel(const IntVector2& position, const IntVector2& screenPosition, int dragButtons, int buttons, Cursor* cursor);
+
+    /// Set whether can be moved.
+    void SetMovable(bool enable);
+    /// Set whether can be resized.
+    void SetResizable(bool enable);
+    /// Set whether resizing width is fixed.
+    void SetFixedWidthResizing(bool enable);
+    /// Set whether resizing height is fixed.
+    void SetFixedHeightResizing(bool enable);
+    /// Set resize area width at edges.
+    void SetResizeBorder(const IntRect& rect);
+    /// Set modal flag. When the modal flag is set, the focused window needs to be dismissed first to allow other UI elements to gain focus.
+    void SetModal(bool modal);
+    /// Set modal shade color.
+    void SetModalShadeColor(const Color& color);
+    /// Set modal frame color.
+    void SetModalFrameColor(const Color& color);
+    /// Set modal frame size.
+    void SetModalFrameSize(const IntVector2& size);
+    /// Set whether model window can be dismissed with the escape key. Default true.
+    void SetModalAutoDismiss(bool enable);
+
+    /// Return whether is movable.
+    bool IsMovable() const { return movable_; }
+
+    /// Return whether is resizable.
+    bool IsResizable() const { return resizable_; }
+
+    /// Return whether is resizing width is fixed.
+    bool GetFixedWidthResizing() const { return fixedWidthResizing_; }
+
+    /// Return whether is resizing height is fixed.
+    bool GetFixedHeightResizing() const { return fixedHeightResizing_; }
+
+    /// Return resize area width at edges.
+    const IntRect& GetResizeBorder() const { return resizeBorder_; }
+
+    /// Return modal flag.
+    bool IsModal() const { return modal_; }
+
+    /// Get modal shade color.
+    const Color& GetModalShadeColor() const { return modalShadeColor_; }
+
+    /// Get modal frame color.
+    const Color& GetModalFrameColor() const { return modalFrameColor_; }
+
+    /// Get modal frame size.
+    const IntVector2& GetModalFrameSize() const { return modalFrameSize_; }
+
+    /// Return whether can be dismissed with escape key.
+    bool GetModalAutoDismiss() const { return modalAutoDismiss_; }
+
+protected:
+    /// Identify drag mode (move/resize.)
+    WindowDragMode GetDragMode(const IntVector2& position) const;
+    /// Set cursor shape based on drag mode.
+    void SetCursorShape(WindowDragMode mode, Cursor* cursor) const;
+    /// Validate window position.
+    void ValidatePosition();
+    /// Check whether alignment supports moving and resizing.
+    bool CheckAlignment() const;
+
+    /// Movable flag.
+    bool movable_;
+    /// Resizable flag.
+    bool resizable_;
+    /// Fixed width resize flag.
+    bool fixedWidthResizing_;
+    /// Fixed height resize flag.
+    bool fixedHeightResizing_;
+    /// Resize area width at edges.
+    IntRect resizeBorder_;
+    /// Current drag mode.
+    WindowDragMode dragMode_;
+    /// Mouse position at drag begin.
+    IntVector2 dragBeginCursor_;
+    /// Original position at drag begin.
+    IntVector2 dragBeginPosition_;
+    /// Original size at drag begin.
+    IntVector2 dragBeginSize_;
+    /// Modal flag.
+    bool modal_;
+    /// Modal auto dismiss (with escape key) flag. Default true.
+    bool modalAutoDismiss_;
+    /// Modal shade color, used when modal flag is set.
+    Color modalShadeColor_;
+    /// Modal frame color, used when modal flag is set.
+    Color modalFrameColor_;
+    /// Modal frame size, used when modal flag is set.
+    IntVector2 modalFrameSize_;
+};
+
+}
+
+}

+ 62 - 1
Source/Atomic/UI/UI.cpp

@@ -57,6 +57,11 @@ using namespace tb;
 #include "UISeparator.h"
 #include "UISeparator.h"
 #include "UIDimmer.h"
 #include "UIDimmer.h"
 
 
+#include "SystemUI/SystemUI.h"
+#include "SystemUI/SystemUIEvents.h"
+#include "SystemUI/DebugHud.h"
+#include "SystemUI/Console.h"
+
 namespace tb
 namespace tb
 {
 {
 
 
@@ -80,7 +85,8 @@ UI::UI(Context* context) :
     inputDisabled_(false),
     inputDisabled_(false),
     keyboardDisabled_(false),
     keyboardDisabled_(false),
     initialized_(false),
     initialized_(false),
-    skinLoaded_(false)
+    skinLoaded_(false),
+    consoleVisible_(false)
 {
 {
 
 
 }
 }
@@ -152,6 +158,7 @@ void UI::Initialize(const String& languageFile)
     SubscribeToEvent(E_KEYUP, HANDLER(UI, HandleKeyUp));
     SubscribeToEvent(E_KEYUP, HANDLER(UI, HandleKeyUp));
     SubscribeToEvent(E_TEXTINPUT, HANDLER(UI, HandleTextInput));
     SubscribeToEvent(E_TEXTINPUT, HANDLER(UI, HandleTextInput));
     SubscribeToEvent(E_UPDATE, HANDLER(UI, HandleUpdate));
     SubscribeToEvent(E_UPDATE, HANDLER(UI, HandleUpdate));
+    SubscribeToEvent(SystemUI::E_CONSOLECLOSED, HANDLER(UI, HandleConsoleClosed));
 
 
     SubscribeToEvent(E_RENDERUPDATE, HANDLER(UI, HandleRenderUpdate));
     SubscribeToEvent(E_RENDERUPDATE, HANDLER(UI, HandleRenderUpdate));
 
 
@@ -159,6 +166,10 @@ void UI::Initialize(const String& languageFile)
 
 
     initialized_ = true;
     initialized_ = true;
 
 
+    SystemUI::SystemUI* systemUI = new SystemUI::SystemUI(context_);
+    context_->RegisterSubsystem(systemUI);
+    systemUI->CreateConsoleAndDebugHud();
+
     //TB_DEBUG_SETTING(LAYOUT_BOUNDS) = 1;
     //TB_DEBUG_SETTING(LAYOUT_BOUNDS) = 1;
 }
 }
 
 
@@ -299,6 +310,8 @@ void UI::Render(bool resetRenderTargets)
 {
 {
     SetVertexData(vertexBuffer_, vertexData_);
     SetVertexData(vertexBuffer_, vertexData_);
     Render(vertexBuffer_, batches_, 0, batches_.Size());
     Render(vertexBuffer_, batches_, 0, batches_.Size());
+
+    GetSubsystem<SystemUI::SystemUI>()->Render();
 }
 }
 
 
 void UI::HandleRenderUpdate(StringHash eventType, VariantMap& eventData)
 void UI::HandleRenderUpdate(StringHash eventType, VariantMap& eventData)
@@ -658,6 +671,54 @@ bool UI::OnWidgetDying(tb::TBWidget *widget)
     return false;
     return false;
 }
 }
 
 
+void UI::ShowDebugHud(bool value)
+{
+    SystemUI::DebugHud* hud = GetSubsystem<SystemUI::DebugHud>();
+
+    if (!hud)
+        return;
+
+    if (value)
+        hud->SetMode(SystemUI::DEBUGHUD_SHOW_ALL);
+    else
+        hud->SetMode(SystemUI::DEBUGHUD_SHOW_NONE);
+}
+
+void UI::ToggleDebugHud()
+{
+    SystemUI::DebugHud* hud = GetSubsystem<SystemUI::DebugHud>();
+
+    if (!hud)
+        return;
+
+    hud->ToggleAll();
+}
 
 
+void UI::ShowConsole(bool value)
+{
+    SystemUI::Console* console = GetSubsystem<SystemUI::Console>();
+
+    if (!console)
+        return;
+
+    console->SetVisible(value);
+    consoleVisible_ = console->IsVisible();
+}
+
+void UI::ToggleConsole()
+{
+    SystemUI::Console* console = GetSubsystem<SystemUI::Console>();
+
+    if (!console)
+        return;
+
+    console->Toggle();
+    consoleVisible_ = console->IsVisible();
+}
+
+void UI::HandleConsoleClosed(StringHash eventType, VariantMap& eventData)
+{
+    consoleVisible_ = false;
+}
 
 
 }
 }

+ 8 - 0
Source/Atomic/UI/UI.h

@@ -62,6 +62,12 @@ public:
 
 
     void GetTBIDString(unsigned id, String& value);
     void GetTBIDString(unsigned id, String& value);
 
 
+    void ShowDebugHud(bool value);
+    void ToggleDebugHud();
+
+    void ShowConsole(bool value);
+    void ToggleConsole();
+
     UIRenderer* GetRenderer() { return renderer_; }
     UIRenderer* GetRenderer() { return renderer_; }
 
 
 private:
 private:
@@ -96,6 +102,7 @@ private:
     bool keyboardDisabled_;
     bool keyboardDisabled_;
     bool initialized_;
     bool initialized_;
     bool skinLoaded_;
     bool skinLoaded_;
+    bool consoleVisible_;
 
 
     // Events
     // Events
     void HandleScreenMode(StringHash eventType, VariantMap& eventData);
     void HandleScreenMode(StringHash eventType, VariantMap& eventData);
@@ -108,6 +115,7 @@ private:
     void HandleKey(bool keydown, int keycode, int scancode);
     void HandleKey(bool keydown, int keycode, int scancode);
     void HandleTextInput(StringHash eventType, VariantMap& eventData);
     void HandleTextInput(StringHash eventType, VariantMap& eventData);
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
+    void HandleConsoleClosed(StringHash eventType, VariantMap& eventData);
 
 
 
 
 };
 };

+ 7 - 7
Source/Atomic/UI/UIInput.cpp

@@ -33,7 +33,7 @@ static int toupr_ascii(int ascii)
 
 
 void UI::HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
 void UI::HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
 {
 {
-    if (inputDisabled_)
+    if (inputDisabled_ || consoleVisible_)
         return;
         return;
 
 
     using namespace MouseButtonDown;
     using namespace MouseButtonDown;
@@ -77,7 +77,7 @@ void UI::HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
 
 
 void UI::HandleMouseButtonUp(StringHash eventType, VariantMap& eventData)
 void UI::HandleMouseButtonUp(StringHash eventType, VariantMap& eventData)
 {
 {
-    if (inputDisabled_)
+    if (inputDisabled_ || consoleVisible_)
         return;
         return;
 
 
     using namespace MouseButtonUp;
     using namespace MouseButtonUp;
@@ -109,7 +109,7 @@ void UI::HandleMouseMove(StringHash eventType, VariantMap& eventData)
 {
 {
     using namespace MouseMove;
     using namespace MouseMove;
 
 
-    if (inputDisabled_)
+    if (inputDisabled_ || consoleVisible_)
         return;
         return;
 
 
     int px = eventData[P_X].GetInt();
     int px = eventData[P_X].GetInt();
@@ -122,7 +122,7 @@ void UI::HandleMouseMove(StringHash eventType, VariantMap& eventData)
 
 
 void UI::HandleMouseWheel(StringHash eventType, VariantMap& eventData)
 void UI::HandleMouseWheel(StringHash eventType, VariantMap& eventData)
 {
 {
-    if (inputDisabled_)
+    if (inputDisabled_ || consoleVisible_)
         return;
         return;
 
 
     using namespace MouseWheel;
     using namespace MouseWheel;
@@ -339,7 +339,7 @@ void UI::HandleKey(bool keydown, int keycode, int scancode)
 
 
 void UI::HandleKeyDown(StringHash eventType, VariantMap& eventData)
 void UI::HandleKeyDown(StringHash eventType, VariantMap& eventData)
 {
 {
-    if (inputDisabled_ || keyboardDisabled_)
+    if (inputDisabled_ || keyboardDisabled_ || consoleVisible_)
         return;
         return;
 
 
     using namespace KeyDown;
     using namespace KeyDown;
@@ -376,7 +376,7 @@ void UI::HandleKeyDown(StringHash eventType, VariantMap& eventData)
 
 
 void UI::HandleKeyUp(StringHash eventType, VariantMap& eventData)
 void UI::HandleKeyUp(StringHash eventType, VariantMap& eventData)
 {
 {
-    if (inputDisabled_ || keyboardDisabled_)
+    if (inputDisabled_ || keyboardDisabled_ || consoleVisible_)
         return;
         return;
 
 
     using namespace KeyUp;
     using namespace KeyUp;
@@ -390,7 +390,7 @@ void UI::HandleKeyUp(StringHash eventType, VariantMap& eventData)
 
 
 void UI::HandleTextInput(StringHash eventType, VariantMap& eventData)
 void UI::HandleTextInput(StringHash eventType, VariantMap& eventData)
 {
 {
-    if (inputDisabled_ || keyboardDisabled_)
+    if (inputDisabled_ || keyboardDisabled_ || consoleVisible_)
         return;
         return;
 
 
     using namespace TextInput;
     using namespace TextInput;

+ 6 - 0
Source/AtomicEditor/EditorMode/AEEditorMode.cpp

@@ -11,6 +11,8 @@
 
 
 #include <AtomicJS/Javascript/JSIPCEvents.h>
 #include <AtomicJS/Javascript/JSIPCEvents.h>
 
 
+#include <Atomic/UI/SystemUI/DebugHud.h>
+
 #include "AEEditorMode.h"
 #include "AEEditorMode.h"
 
 
 using namespace ToolCore;
 using namespace ToolCore;
@@ -32,6 +34,10 @@ EditorMode::~EditorMode()
 void EditorMode::HandleIPCWorkerStarted(StringHash eventType, VariantMap& eventData)
 void EditorMode::HandleIPCWorkerStarted(StringHash eventType, VariantMap& eventData)
 {
 {
     VariantMap startupData;
     VariantMap startupData;
+    SystemUI::DebugHud* debugHud = GetSubsystem<SystemUI::DebugHud>();
+
+    startupData["debugHudMode"] = debugHud ? debugHud->GetMode() : (unsigned) 0;
+
     playerBroker_->PostMessage(E_IPCINITIALIZE, startupData);
     playerBroker_->PostMessage(E_IPCINITIALIZE, startupData);
 
 
     SendEvent("EditorPlayerStarted");
     SendEvent("EditorPlayerStarted");

+ 5 - 0
Source/AtomicEditor/PlayerMode/AEPlayerMode.cpp

@@ -3,6 +3,7 @@
 #include <Atomic/IO/Log.h>
 #include <Atomic/IO/Log.h>
 #include <Atomic/Input/InputEvents.h>
 #include <Atomic/Input/InputEvents.h>
 #include <Atomic/Core/ProcessUtils.h>
 #include <Atomic/Core/ProcessUtils.h>
+#include <Atomic/UI/SystemUI/DebugHud.h>
 #include <Atomic/IPC/IPCEvents.h>
 #include <Atomic/IPC/IPCEvents.h>
 #include <Atomic/IPC/IPCWorker.h>
 #include <Atomic/IPC/IPCWorker.h>
 
 
@@ -45,6 +46,10 @@ void PlayerMode::HandleIPCInitialize(StringHash eventType, VariantMap& eventData
         SendEvent(E_EXITREQUESTED);
         SendEvent(E_EXITREQUESTED);
     }
     }
 
 
+    SystemUI::DebugHud* debugHud = GetSubsystem<SystemUI::DebugHud>();
+    if (debugHud)
+        debugHud->SetMode(eventData["debugHudMode"].GetUInt());
+
 }
 }
 
 
 void PlayerMode::ProcessArguments() {
 void PlayerMode::ProcessArguments() {