Просмотр исходного кода

Added a GUISliderField and extended normal sliders with a range and step increment

BearishSun 10 лет назад
Родитель
Сommit
bf16de13f1

+ 2 - 0
BansheeEditor/BansheeEditor.vcxproj

@@ -299,6 +299,7 @@
     <ClInclude Include="Include\BsGUIIntField.h" />
     <ClInclude Include="Include\BsGUIDropButton.h" />
     <ClInclude Include="Include\BsGUIListBoxField.h" />
+    <ClInclude Include="Include\BsGUISliderField.h" />
     <ClInclude Include="Include\BsGUIStatusBar.h" />
     <ClInclude Include="Include\BsGUITextField.h" />
     <ClInclude Include="Include\BsGUIToggleField.h" />
@@ -390,6 +391,7 @@
     <ClCompile Include="Source\BsGUIDropButton.cpp" />
     <ClCompile Include="Source\BsGUIResourceTreeView.cpp" />
     <ClCompile Include="Source\BsGUISceneTreeView.cpp" />
+    <ClCompile Include="Source\BsGUISliderField.cpp" />
     <ClCompile Include="Source\BsGUIStatusBar.cpp" />
     <ClCompile Include="Source\BsGUITabbedTitleBar.cpp" />
     <ClCompile Include="Source\BsGUITabButton.cpp" />

+ 6 - 0
BansheeEditor/BansheeEditor.vcxproj.filters

@@ -288,6 +288,9 @@
     <ClInclude Include="Include\BsGUIListBoxField.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="Include\BsGUISliderField.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsEditorCommand.cpp">
@@ -515,5 +518,8 @@
     <ClCompile Include="Source\BsGUIListBoxField.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="Source\BsGUISliderField.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 1 - 0
BansheeEditor/Include/BsEditorPrerequisites.h

@@ -39,6 +39,7 @@ namespace BansheeEngine
 	class GUIVector4Field;
 	class GUIColorField;
 	class GUIListBoxField;
+	class GUISliderField;
 	class GUIColor;
 	class GUIStatusBar;
 	class GUIDropButton;

+ 110 - 0
BansheeEditor/Include/BsGUISliderField.h

@@ -0,0 +1,110 @@
+#pragma once
+
+#include "BsEditorPrerequisites.h"
+#include "BsGUIFieldBase.h"
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	A composite GUI object representing an editor field. Editor fields are a combination
+	 *			of a label and an input field. Label is optional. This specific implementation
+	 *			displays a horizontal slider and a floating point input box.
+	 */
+	class BS_ED_EXPORT GUISliderField : public TGUIField<GUISliderField>
+	{
+	public:
+		/**
+		 * Returns type name of the GUI element used for finding GUI element styles. 
+		 */
+		static const String& getGUITypeName();
+
+		/**
+		 * Style type name for the internal input box.
+		 */
+		static const String& getInputStyleType();
+
+		/**
+		 * Style type name for the internal slider.
+		 */
+		static const String& getSliderStyleType();
+
+		GUISliderField(const PrivatelyConstruct& dummy, const GUIContent& labelContent, UINT32 labelWidth,
+			const String& style, const GUIDimensions& dimensions, bool withLabel);
+
+		/**
+		 * @brief	Returns the value of the input field/slider.
+		 */
+		float getValue() const;
+
+		/**
+		 * @brief	Sets a new value in the input field/slider.
+		 */
+		void setValue(float value);
+
+		/**
+		 * @brief	Sets a minimum and maximum allow values in the input field.
+		 *			Set to large negative/positive values if you don't require clamping.
+		 */
+		void setRange(float min, float max);
+
+		/**
+		 * @brief	Sets a step that defines the minimal increment the value can be increased/decreased by. Set to zero
+		 * 			to have no step.
+		 */
+		void setStep(float step);
+
+		/**
+		 * @brief	Checks is the input field currently active.
+		 */
+		bool hasInputFocus() const { return mHasInputFocus; }
+
+		/**
+		 * @copydoc	GUIElement::setTint
+		 */
+		virtual void setTint(const Color& color) override;
+
+		Event<void(float)> onValueChanged; /**< Triggers when the field value changes. */
+	protected:
+		virtual ~GUISliderField();
+
+		/**
+		 * @copydoc	GUIElementContainer::updateClippedBounds
+		 */
+		void updateClippedBounds() override;
+
+		/**
+		 * @copydoc	GUIElementContainer::styleUpdated
+		 */
+		void styleUpdated() override;
+
+		/**
+		 * @brief	Triggered when the input box value changes.
+		 */
+		void valueChanged(const WString& newValue);
+
+		/**
+		 * @brief	Triggered when the slider is moved.
+		 */
+		void sliderChanged(float newValue);
+
+		/**
+		 * @brief	Triggers when the input box receives keyboard focus.
+		 */
+		void focusGained();
+
+		/**
+		 * @brief	Triggers when the input box loses keyboard focus.
+		 */
+		void focusLost();
+
+		/**
+		 * @brief	Callback that checks can the provided string be
+		 *			converted to a floating point value.
+		 */
+		static bool floatFilter(const WString& str);
+
+		GUIInputBox* mInputBox;
+		GUISliderHorz* mSlider;
+		bool mHasInputFocus;
+	};
+}

+ 11 - 0
BansheeEditor/Source/BsBuiltinEditorResources.cpp

@@ -21,6 +21,7 @@
 #include "BsGUIVector3Field.h"
 #include "BsGUIVector4Field.h"
 #include "BsGUIListBoxField.h"
+#include "BsGUISliderField.h"
 #include "BsGUIProgressBar.h"
 #include "BsGUISlider.h"
 #include "BsGUIDropDownContent.h"
@@ -1354,6 +1355,16 @@ namespace BansheeEngine
 
 		skin->setStyle(GUIListBoxField::getGUITypeName(), editorListBoxFieldStyle);
 
+		GUIElementStyle editorSliderFieldStyle;
+		editorSliderFieldStyle.fixedHeight = true;
+		editorSliderFieldStyle.height = 30;
+		editorSliderFieldStyle.minWidth = 30;
+		editorSliderFieldStyle.subStyles[GUISliderField::getLabelStyleType()] = GUISliderField::getLabelStyleType();
+		editorSliderFieldStyle.subStyles[GUISliderField::getInputStyleType()] = GUIInputBox::getGUITypeName();
+		editorSliderFieldStyle.subStyles[GUISliderField::getSliderStyleType()] = GUISliderHorz::getGUITypeName();
+
+		skin->setStyle(GUISliderField::getGUITypeName(), editorSliderFieldStyle);
+
 		/************************************************************************/
 		/* 							     FOLDOUT                      		    */
 		/************************************************************************/

+ 158 - 0
BansheeEditor/Source/BsGUISliderField.cpp

@@ -0,0 +1,158 @@
+#include "BsGUISliderField.h"
+#include "BsGUILayout.h"
+#include "BsGUILabel.h"
+#include "BsGUIInputBox.h"
+#include "BsGUISpace.h"
+#include "BsBuiltinResources.h"
+#include "BsCGUIWidget.h"
+#include "BsGUIMouseEvent.h"
+#include "BsCursor.h"
+#include "BsCGUIWidget.h"
+#include "BsViewport.h"
+#include "BsGUISlider.h"
+#include "BsCmdInputFieldValueChange.h"
+#include <regex>
+
+using namespace std::placeholders;
+
+namespace BansheeEngine
+{
+	GUISliderField::GUISliderField(const PrivatelyConstruct& dummy, const GUIContent& labelContent, UINT32 labelWidth,
+		const String& style, const GUIDimensions& dimensions, bool withLabel)
+		:TGUIField(dummy, labelContent, labelWidth, style, dimensions, withLabel), mInputBox(nullptr), mSlider(nullptr),
+		mHasInputFocus(false)
+	{
+		mSlider = GUISliderHorz::create(GUIOptions(GUIOption::flexibleWidth()), getSubStyleName(getSliderStyleType()));
+		mSlider->onChanged.connect(std::bind(&GUISliderField::sliderChanged, this, _1));
+
+		mInputBox = GUIInputBox::create(false, GUIOptions(GUIOption::fixedWidth(75)), getSubStyleName(getInputStyleType()));
+		mInputBox->setFilter(&GUISliderField::floatFilter);
+
+		mInputBox->onValueChanged.connect(std::bind((void(GUISliderField::*)(const WString&))&GUISliderField::valueChanged, this, _1));
+		mInputBox->onFocusGained.connect(std::bind(&GUISliderField::focusGained, this));
+		mInputBox->onFocusLost.connect(std::bind(&GUISliderField::focusLost, this));
+
+		mLayout->addElement(mSlider);
+		mLayout->addNewElement<GUIFixedSpace>(5);
+		mLayout->addElement(mInputBox);
+
+		setValue(0);
+		mInputBox->setText(L"0");
+	}
+
+	GUISliderField::~GUISliderField()
+	{
+
+	}
+
+	float GUISliderField::getValue() const
+	{
+		return mSlider->getValue();
+	}
+
+	void GUISliderField::setValue(float value)
+	{
+		bool changed = false;
+
+		float origValue = mSlider->getValue();
+		if (origValue != value)
+		{
+			mSlider->setValue(value);
+			changed = true;
+		}
+
+		float clampedValue = mSlider->getValue();
+
+		// Only update with new value if it actually changed, otherwise
+		// problems can occur when user types in "0." and the field
+		// updates back to "0" effectively making "." unusable
+		float curValue = parseFloat(mInputBox->getText());
+		if (clampedValue != curValue)
+		{
+			mInputBox->setText(toWString(clampedValue));
+			changed = true;
+		}
+
+		if (changed)
+			onValueChanged(clampedValue);
+	}
+
+	void GUISliderField::setRange(float min, float max)
+	{
+		mSlider->setRange(min, max);
+	}
+
+	void GUISliderField::setStep(float step)
+	{
+		mSlider->setStep(step);
+	}
+
+	void GUISliderField::setTint(const Color& color)
+	{
+		if (mLabel != nullptr)
+			mLabel->setTint(color);
+
+		mInputBox->setTint(color);
+	}
+
+	void GUISliderField::updateClippedBounds()
+	{
+		mClippedBounds = mLayoutData.area;
+	}
+
+	const String& GUISliderField::getGUITypeName()
+	{
+		static String typeName = "GUISliderField";
+		return typeName;
+	}
+
+	const String& GUISliderField::getInputStyleType()
+	{
+		static String LABEL_STYLE_TYPE = "EditorFieldInput";
+		return LABEL_STYLE_TYPE;
+	}
+
+	const String& GUISliderField::getSliderStyleType()
+	{
+		static String SLIDER_STYLE_TYPE = "EditorSliderInput";
+		return SLIDER_STYLE_TYPE;
+	}
+
+	void GUISliderField::styleUpdated()
+	{
+		if (mLabel != nullptr)
+			mLabel->setStyle(getSubStyleName(getLabelStyleType()));
+
+		mSlider->setStyle(getSubStyleName(getSliderStyleType()));
+		mInputBox->setStyle(getSubStyleName(getInputStyleType()));
+	}
+
+	void GUISliderField::valueChanged(const WString& newValue)
+	{
+		float newFloatValue = parseFloat(newValue);
+
+		CmdInputFieldValueChange<GUISliderField, float>::execute(this, newFloatValue);
+	}
+
+	void GUISliderField::sliderChanged(float newValue)
+	{
+		setValue(mSlider->getValue());
+	}
+
+	void GUISliderField::focusGained()
+	{
+		UndoRedo::instance().pushGroup("InputBox");
+		mHasInputFocus = true;
+	}
+
+	void GUISliderField::focusLost()
+	{
+		UndoRedo::instance().popGroup("InputBox");
+		mHasInputFocus = false;
+	}
+
+	bool GUISliderField::floatFilter(const WString& str)
+	{
+		return std::regex_match(str, std::wregex(L"-?(\\d+(\\.\\d*)?)?"));
+	}
+}

+ 2 - 2
BansheeEngine/Include/BsGUIElement.h

@@ -235,7 +235,7 @@ namespace BansheeEngine
 		 *
 		 * @note	Internal method.
 		 */
-		Type _getType() const { return GUIElementBase::Type::Element; }
+		Type _getType() const override { return GUIElementBase::Type::Element; }
 
 		/**
 		 * @brief	Checks if element has been destroyed and is queued for deletion.
@@ -271,7 +271,7 @@ namespace BansheeEngine
 		 *
 		 * @note	Internal method.
 		 */
-		const RectOffset& _getPadding() const;
+		const RectOffset& _getPadding() const override;
 
 		/**
 		 * @brief	Returns GUI element depth. This includes widget and area depth, but does not

+ 25 - 0
BansheeEngine/Include/BsGUISlider.h

@@ -39,6 +39,29 @@ namespace BansheeEngine
 		 */
 		float getPercent() const;
 
+		/**
+		 * @brief	Gets the current value of the slider. This is the slider handle position percentage scaled within
+		 * 			the current minimum and maximum range, rounded up to nearest step increment.
+		 */
+		float getValue() const;
+
+		/**
+		 * @brief	Sets a new value of the slider. This value should be within minimum and maximum range values.
+		 */
+		void setValue(float value);
+
+		/**
+		 * @brief	Sets a minimum and maximum allow values in the input field.
+		 *			Set to large negative/positive values if you don't require clamping.
+		 */
+		void setRange(float min, float max);
+
+		/**
+		 * @brief	Sets a step that defines the minimal increment the value can be increased/decreased by. Set to zero
+		 * 			to have no step.
+		 */
+		void setStep(float step);
+
 		/**
 		 * @copydoc	GUIElement::setTint
 		 */
@@ -74,6 +97,8 @@ namespace BansheeEngine
 		GUITexture* mBackground;
 		GUITexture* mFillBackground;
 		bool mHorizontal;
+		float mMinRange;
+		float mMaxRange;
 
 		HEvent mHandleMovedConn;
 	};

+ 12 - 0
BansheeEngine/Include/BsGUISliderHandle.h

@@ -92,6 +92,12 @@ namespace BansheeEngine
 		 */
 		UINT32 getMaxSize() const;
 
+		/**
+		 * @brief	Sets a step that defines the minimal increment the value can be increased/decreased by. Set to zero
+		 * 			to have no step.
+		 */
+		void setStep(float step);
+
 		/**
 		 * @copydoc	GUIElement::setTint
 		 */
@@ -150,6 +156,11 @@ namespace BansheeEngine
 		 */
 		bool isOnHandle(const Vector2I& pos) const;
 
+		/**
+		 * @brief	Sets the position of the slider handle, in pixels. Relative to this object. For internal use only.
+		 */
+		void setHandlePosPx(INT32 pos);
+
 		/**
 		 * @brief	Gets the currently active texture, depending on handle state.
 		 */
@@ -161,6 +172,7 @@ namespace BansheeEngine
 		bool mHorizontal; // Otherwise its vertical
 		bool mJumpOnClick;
 		float mPctHandlePos;
+		float mStep;
 		INT32 mDragStartPos;
 		bool mMouseOverHandle;
 		bool mHandleDragged;

+ 26 - 1
BansheeEngine/Source/BsGUISlider.cpp

@@ -11,7 +11,7 @@ using namespace std::placeholders;
 namespace BansheeEngine
 {
 	GUISlider::GUISlider(bool horizontal, const String& styleName, const GUIDimensions& dimensions)
-		:GUIElementContainer(dimensions, styleName), mHorizontal(horizontal)
+		:GUIElementContainer(dimensions, styleName), mHorizontal(horizontal), mMinRange(0.0f), mMaxRange(1.0f)
 	{
 		mSliderHandle = GUISliderHandle::create(horizontal, true, getSubStyleName(getHandleStyleType()));
 		mBackground = GUITexture::create(getSubStyleName(getBackgroundStyleType()));
@@ -147,6 +147,31 @@ namespace BansheeEngine
 		return mSliderHandle->getHandlePos();
 	}
 
+	float GUISlider::getValue() const
+	{
+		float diff = mMaxRange - mMinRange;
+		return mMinRange + diff * mSliderHandle->getHandlePos();
+	}
+
+	void GUISlider::setValue(float value)
+	{
+		float diff = mMaxRange - mMinRange;
+		float pct = value / diff;
+
+		setPercent(pct);
+	}
+
+	void GUISlider::setRange(float min, float max)
+	{
+		mMinRange = min;
+		mMaxRange = max;
+	}
+
+	void GUISlider::setStep(float step)
+	{
+		mSliderHandle->setStep(step);
+	}
+
 	void GUISlider::setTint(const Color& color)
 	{
 		mBackground->setTint(color);

+ 34 - 14
BansheeEngine/Source/BsGUISliderHandle.cpp

@@ -19,7 +19,7 @@ namespace BansheeEngine
 
 	GUISliderHandle::GUISliderHandle(bool horizontal, bool jumpOnClick, const String& styleName, const GUIDimensions& dimensions)
 		:GUIElement(styleName, dimensions), mHorizontal(horizontal), mHandleSize(0), mMouseOverHandle(false), mPctHandlePos(0.0f), mDragStartPos(0),
-		mHandleDragged(false), mState(State::Normal), mJumpOnClick(jumpOnClick)
+		mHandleDragged(false), mState(State::Normal), mJumpOnClick(jumpOnClick), mStep(0.0f)
 	{
 		mImageSprite = bs_new<ImageSprite>();
 	}
@@ -48,7 +48,11 @@ namespace BansheeEngine
 
 	void GUISliderHandle::_setHandlePos(float pct)
 	{
-		mPctHandlePos = Math::clamp01(pct);
+		float maxPct = 1.0f;
+		if (mStep > 0.0f)
+			maxPct = Math::floor(1.0f / mStep) * mStep;
+
+		mPctHandlePos = Math::clamp(pct, 0.0f, maxPct);
 	}
 
 	float GUISliderHandle::getHandlePos() const
@@ -56,6 +60,11 @@ namespace BansheeEngine
 		return mPctHandlePos;;
 	}
 
+	void GUISliderHandle::setStep(float step)
+	{
+		mStep = Math::clamp01(step);
+	}
+
 	UINT32 GUISliderHandle::getScrollableSize() const
 	{
 		return getMaxSize() - mHandleSize;
@@ -197,8 +206,7 @@ namespace BansheeEngine
 				else
 					handlePosPx = (float)(ev.getPosition().y - (INT32)mLayoutData.area.y - mHandleSize * 0.5f);
 
-				float maxScrollAmount = (float)getMaxSize() - mHandleSize;
-				mPctHandlePos = Math::clamp01(handlePosPx / maxScrollAmount);
+				setHandlePosPx((INT32)handlePosPx);
 			}
 
 			if(mHorizontal)
@@ -228,9 +236,7 @@ namespace BansheeEngine
 				handlePosPx = (float)(ev.getPosition().y - mDragStartPos - mLayoutData.area.y);
 			}
 
-			float maxScrollAmount = (float)getMaxSize() - mHandleSize;
-			mPctHandlePos = Math::clamp01(handlePosPx / maxScrollAmount);
-
+			setHandlePosPx((INT32)handlePosPx);
 			onHandleMoved(mPctHandlePos);
 
 			_markLayoutAsDirty();
@@ -261,6 +267,12 @@ namespace BansheeEngine
 			INT32 handlePosPx = getHandlePosPx();
 			if (!mJumpOnClick)
 			{
+				UINT32 stepSizePx = 0;
+				if (mStep > 0.0f)
+					stepSizePx = (UINT32)(mStep * getMaxSize());
+				else
+					stepSizePx = mHandleSize;
+
 				INT32 handleOffset = 0;
 				if (mHorizontal)
 				{
@@ -268,9 +280,9 @@ namespace BansheeEngine
 					INT32 handleRight = handleLeft + mHandleSize;
 
 					if (ev.getPosition().x < handleLeft)
-						handleOffset -= mHandleSize;
+						handleOffset -= stepSizePx;
 					else if (ev.getPosition().x > handleRight)
-						handleOffset += mHandleSize;
+						handleOffset += stepSizePx;
 				}
 				else
 				{
@@ -278,17 +290,15 @@ namespace BansheeEngine
 					INT32 handleBottom = handleTop + mHandleSize;
 
 					if (ev.getPosition().y < handleTop)
-						handleOffset -= mHandleSize;
+						handleOffset -= stepSizePx;
 					else if (ev.getPosition().y > handleBottom)
-						handleOffset += mHandleSize;
+						handleOffset += stepSizePx;
 				}
 
 				handlePosPx += handleOffset;
 			}
 
-			float maxScrollAmount = (float)getMaxSize() - mHandleSize;
-			mPctHandlePos = Math::clamp01(handlePosPx / maxScrollAmount);
-
+			setHandlePosPx(handlePosPx);
 			onHandleMoved(mPctHandlePos);
 
 			mHandleDragged = false;
@@ -340,6 +350,16 @@ namespace BansheeEngine
 		return Math::floorToInt(mPctHandlePos * maxScrollAmount);
 	}
 
+	void GUISliderHandle::setHandlePosPx(INT32 pos)
+	{
+		float maxScrollAmount = (float)getMaxSize() - mHandleSize;
+		float maxPct = 1.0f;
+		if (mStep > 0.0f)
+			maxPct = Math::floor(1.0f / mStep) * mStep;
+
+		mPctHandlePos = Math::clamp(pos / maxScrollAmount, 0.0f, maxPct);
+	}
+
 	UINT32 GUISliderHandle::getMaxSize() const
 	{
 		UINT32 maxSize = mLayoutData.area.height;

+ 134 - 0
MBansheeEditor/GUI/GUISliderField.cs

@@ -0,0 +1,134 @@
+using System;
+using System.Runtime.CompilerServices;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+    /// <summary>
+    /// Editor GUI element that displays a slider with floating point input field and an optional label.
+    /// </summary>
+    public sealed class GUISliderField : GUIElement
+    {
+        public delegate void OnChangedDelegate(float newValue);
+
+        /// <summary>
+        /// Triggered when the value in the field changes.
+        /// </summary>
+        public event OnChangedDelegate OnChanged;
+
+        /// <summary>
+        /// Value displayed by the field input box.
+        /// </summary>
+        public float Value
+        {
+            get { return Internal_GetValue(mCachedPtr); }
+            set { Internal_SetValue(mCachedPtr, value); }
+        }
+
+        /// <summary>
+        /// Creates a new slider field element with a label.
+        /// </summary>
+        /// <param name="min">Minimum boundary of the range to clamp values to.</param>
+        /// <param name="max">Maximum boundary of the range to clamp values to.</param>
+        /// <param name="title">Content to display on the label.</param>
+        /// <param name="titleWidth">Width of the title label in pixels.</param>
+        /// <param name="style">Optional style to use for the element. Style controls the look of the element, as well as 
+        ///                     default layout options. Style will be retrieved from the active GUISkin. If not specified 
+        ///                     default element style is used.</param>
+        /// <param name="options">Options that allow you to control how is the element  positioned and sized. This will 
+        ///                       override any similar options set by style.</param>
+        public GUISliderField(float min, float max, GUIContent title, int titleWidth = 100, 
+            string style = "", params GUIOption[] options)
+        {
+            Internal_CreateInstance(this, min, max, title, titleWidth, style, options, true);
+        }
+
+        /// <summary>
+        /// Creates a new slider field element without a label.
+        /// </summary>
+        /// <param name="min">Minimum boundary of the range to clamp values to.</param>
+        /// <param name="max">Maximum boundary of the range to clamp values to.</param>
+        /// <param name="style">Optional style to use for the element. Style controls the look of the element, as well as 
+        ///                     default layout options. Style will be retrieved from the active GUISkin. If not specified 
+        ///                     default element style is used.</param>
+        /// <param name="options">Options that allow you to control how is the element  positioned and sized. This will 
+        ///                       override any similar options set by style.</param>
+        public GUISliderField(float min, float max, string style = "", params GUIOption[] options)
+        {
+            Internal_CreateInstance(this, min, max, null, 0, style, options, false);
+        }
+
+        /// <summary>
+        /// Checks does the element currently has input focus. Input focus means the element has an input caret displayed
+        /// and will accept input from the keyboard.
+        /// </summary>
+        /// <returns>True if the element has input focus.</returns>
+        public bool HasInputFocus()
+        {
+            bool value;
+            Internal_HasInputFocus(mCachedPtr, out value);
+            return value;
+        }
+
+        /// <summary>
+        /// Colors the element with a specific tint.
+        /// </summary>
+        /// <param name="color">Tint to apply to the element.</param>
+        public void SetTint(Color color)
+        {
+            Internal_SetTint(mCachedPtr, color);
+        }
+
+        /// <summary>
+        /// Sets a range that will input field values will be clamped to. Set to large negative/positive values if clamping
+        /// is not required.
+        /// </summary>
+        /// <param name="min">Minimum boundary of the range to clamp values to.</param>
+        /// <param name="max">Maximum boundary of the range to clamp values to.</param>
+        public void SetRange(float min, float max)
+        {
+            Internal_SetRange(mCachedPtr, min, max);
+        }
+
+        /// <summary>
+        /// Sets a step value that determines the minimal increment the slider can be increased or decreased by.
+        /// </summary>
+        /// <param name="step">Step value in percent if range is not defined, otherwise in same units as the range.</param>
+        public void SetStep(float step)
+        {
+            Internal_SetStep(mCachedPtr, step);
+        }
+
+        /// <summary>
+        /// Triggered by the runtime when the value of the float field changes.
+        /// </summary>
+        /// <param name="newValue">New value of the float field.</param>
+        private void DoOnChanged(float newValue)
+        {
+            if (OnChanged != null)
+                OnChanged(newValue);
+        }
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_CreateInstance(GUISliderField instance, float min, float max, 
+            GUIContent title, int titleWidth, string style, GUIOption[] options, bool withTitle);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern float Internal_GetValue(IntPtr nativeInstance);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_SetValue(IntPtr nativeInstance, float value);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_HasInputFocus(IntPtr nativeInstance, out bool value);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_SetTint(IntPtr nativeInstance, Color color);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_SetRange(IntPtr nativeInstance, float min, float max);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_SetStep(IntPtr nativeInstance, float step);
+    }
+}

+ 1 - 0
MBansheeEditor/MBansheeEditor.csproj

@@ -50,6 +50,7 @@
     <Compile Include="GUI\GUIEnumField.cs" />
     <Compile Include="GUI\GUIListBoxField.cs" />
     <Compile Include="GUI\GUISceneTreeView.cs" />
+    <Compile Include="GUI\GUISliderField.cs" />
     <Compile Include="GUI\TextureField.cs" />
     <Compile Include="HierarchyWindow.cs" />
     <Compile Include="Inspectors\CameraInspector.cs" />

+ 84 - 0
MBansheeEngine/GUI/GUISlider.cs

@@ -25,6 +25,16 @@ namespace BansheeEngine
             set { Internal_SetPercent(mCachedPtr, value); }
         }
 
+        /// <summary>
+        /// Returns the position of the slider handle, in range determined by <see cref="SetRange"/>. If range is not defined
+        /// set to [0, 1] this is equivalent of <see cref="Percent"/>.
+        /// </summary>
+        public float Value
+        {
+            get { return Internal_GetValue(mCachedPtr); }
+            set { Internal_SetValue(mCachedPtr, value); }
+        }
+
         /// <summary>
         /// Creates a new horizontal slider.
         /// </summary>
@@ -49,6 +59,26 @@ namespace BansheeEngine
             Internal_CreateInstance(this, style, new GUIOption[0]);
         }
 
+        /// <summary>
+        /// Sets a range that will input field values will be clamped to. Set to large negative/positive values if clamping
+        /// is not required.
+        /// </summary>
+        /// <param name="min">Minimum boundary of the range to clamp values to.</param>
+        /// <param name="max">Maximum boundary of the range to clamp values to.</param>
+        public void SetRange(float min, float max)
+        {
+            Internal_SetRange(mCachedPtr, min, max);
+        }
+
+        /// <summary>
+        /// Sets a step value that determines the minimal increment the slider can be increased or decreased by.
+        /// </summary>
+        /// <param name="step">Step value in percent if range is not defined, otherwise in same units as the range.</param>
+        public void SetStep(float step)
+        {
+            Internal_SetStep(mCachedPtr, step);
+        }
+
         /// <summary>
         /// Colors the element with a specific tint.
         /// </summary>
@@ -77,6 +107,18 @@ namespace BansheeEngine
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_SetPercent(IntPtr nativeInstance, float percent);
 
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern float Internal_GetValue(IntPtr nativeInstance);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_SetValue(IntPtr nativeInstance, float percent);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_SetRange(IntPtr nativeInstance, float min, float max);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_SetStep(IntPtr nativeInstance, float step);
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_SetTint(IntPtr nativeInstance, Color color);
     }
@@ -103,6 +145,16 @@ namespace BansheeEngine
             set { Internal_SetPercent(mCachedPtr, value); }
         }
 
+        /// <summary>
+        /// Returns the position of the slider handle, in range determined by <see cref="SetRange"/>. If range is not defined
+        /// set to [0, 1] this is equivalent of <see cref="Percent"/>.
+        /// </summary>
+        public float Value
+        {
+            get { return Internal_GetValue(mCachedPtr); }
+            set { Internal_SetValue(mCachedPtr, value); }
+        }
+
         /// <summary>
         /// Creates a new vertical slider.
         /// </summary>
@@ -127,6 +179,26 @@ namespace BansheeEngine
             Internal_CreateInstance(this, style, new GUIOption[0]);
         }
 
+        /// <summary>
+        /// Sets a range that will input field values will be clamped to. Set to large negative/positive values if clamping
+        /// is not required.
+        /// </summary>
+        /// <param name="min">Minimum boundary of the range to clamp values to.</param>
+        /// <param name="max">Maximum boundary of the range to clamp values to.</param>
+        public void SetRange(float min, float max)
+        {
+            Internal_SetRange(mCachedPtr, min, max);
+        }
+
+        /// <summary>
+        /// Sets a step value that determines the minimal increment the slider can be increased or decreased by.
+        /// </summary>
+        /// <param name="step">Step value in percent if range is not defined, otherwise in same units as the range.</param>
+        public void SetStep(float step)
+        {
+            Internal_SetStep(mCachedPtr, step);
+        }
+
         /// <summary>
         /// Colors the element with a specific tint.
         /// </summary>
@@ -155,6 +227,18 @@ namespace BansheeEngine
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_SetPercent(IntPtr nativeInstance, float percent);
 
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern float Internal_GetValue(IntPtr nativeInstance);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_SetValue(IntPtr nativeInstance, float percent);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_SetRange(IntPtr nativeInstance, float min, float max);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_SetStep(IntPtr nativeInstance, float step);
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_SetTint(IntPtr nativeInstance, Color color);
     }

+ 44 - 0
SBansheeEditor/Include/BsScriptGUISliderField.h

@@ -0,0 +1,44 @@
+#pragma once
+
+#include "BsScriptEditorPrerequisites.h"
+#include "BsScriptGUIElement.h"
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	Interop class between C++ & CLR for GUISliderField.
+	 */
+	class BS_SCR_BED_EXPORT ScriptGUISliderField : public TScriptGUIElement<ScriptGUISliderField>
+	{
+	public:
+		SCRIPT_OBJ(EDITOR_ASSEMBLY, "BansheeEditor", "GUISliderField")
+
+	private:
+		ScriptGUISliderField(MonoObject* instance, GUISliderField* sliderField);
+
+		/**
+		 * @brief	Triggered when the value in the native slider field changes.
+		 *
+		 * @param	instance	Managed GUISliderField instance.
+		 * @param	newValue	New field value.
+		 */
+		static void onChanged(MonoObject* instance, float newValue);
+
+		/************************************************************************/
+		/* 								CLR HOOKS						   		*/
+		/************************************************************************/
+		static void internal_createInstance(MonoObject* instance, float min, float max, MonoObject* title, UINT32 titleWidth,
+			MonoString* style, MonoArray* guiOptions, bool withTitle);
+
+		static float internal_getValue(ScriptGUISliderField* nativeInstance);
+		static void internal_setValue(ScriptGUISliderField* nativeInstance, float value);
+		static void internal_hasInputFocus(ScriptGUISliderField* nativeInstance, bool* output);
+		static void internal_setTint(ScriptGUISliderField* nativeInstance, Color color);
+		static void internal_setRange(ScriptGUISliderField* nativeInstance, float min, float max);
+		static void internal_setStep(ScriptGUISliderField* nativeInstance, float step);
+
+		typedef void(__stdcall *OnChangedThunkDef) (MonoObject*, float, MonoException**);
+
+		static OnChangedThunkDef onChangedThunk;
+	};
+}

+ 2 - 0
SBansheeEditor/SBansheeEditor.vcxproj

@@ -249,6 +249,7 @@
     <ClInclude Include="Include\BsScriptGUIEnumField.h" />
     <ClInclude Include="Include\BsScriptGUIListBoxField.h" />
     <ClInclude Include="Include\BsScriptGUISceneTreeView.h" />
+    <ClInclude Include="Include\BsScriptGUISliderField.h" />
     <ClInclude Include="Include\BsScriptGUITextureField.h" />
     <ClInclude Include="Include\BsScriptInspectorUtility.h" />
     <ClInclude Include="Include\BsScriptOSDropTarget.h" />
@@ -306,6 +307,7 @@
     <ClCompile Include="Source\BsScriptGUIEnumField.cpp" />
     <ClCompile Include="Source\BsScriptGUIListBoxField.cpp" />
     <ClCompile Include="Source\BsScriptGUISceneTreeView.cpp" />
+    <ClCompile Include="Source\BsScriptGUISliderField.cpp" />
     <ClCompile Include="Source\BsScriptGUITextureField.cpp" />
     <ClCompile Include="Source\BsScriptInspectorUtility.cpp" />
     <ClCompile Include="Source\BsScriptOSDropTarget.cpp" />

+ 6 - 0
SBansheeEditor/SBansheeEditor.vcxproj.filters

@@ -180,6 +180,9 @@
     <ClInclude Include="Include\BsScriptGUIEnumField.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="Include\BsScriptGUISliderField.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsScriptEditorPlugin.cpp">
@@ -347,5 +350,8 @@
     <ClCompile Include="Source\BsScriptGUIEnumField.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="Source\BsScriptGUISliderField.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 106 - 0
SBansheeEditor/Source/BsScriptGUISliderField.cpp

@@ -0,0 +1,106 @@
+#include "BsScriptGUISliderField.h"
+#include "BsScriptMeta.h"
+#include "BsMonoField.h"
+#include "BsMonoClass.h"
+#include "BsMonoManager.h"
+#include "BsMonoMethod.h"
+#include "BsMonoUtil.h"
+#include "BsGUISliderField.h"
+#include "BsGUIOptions.h"
+#include "BsGUIContent.h"
+#include "BsScriptGUIContent.h"
+
+using namespace std::placeholders;
+
+namespace BansheeEngine
+{
+	ScriptGUISliderField::OnChangedThunkDef ScriptGUISliderField::onChangedThunk;
+
+	ScriptGUISliderField::ScriptGUISliderField(MonoObject* instance, GUISliderField* sliderField)
+		:TScriptGUIElement(instance, sliderField)
+	{
+
+	}
+
+	void ScriptGUISliderField::initRuntimeData()
+	{
+		metaData.scriptClass->addInternalCall("Internal_CreateInstance", &ScriptGUISliderField::internal_createInstance);
+		metaData.scriptClass->addInternalCall("Internal_GetValue", &ScriptGUISliderField::internal_getValue);
+		metaData.scriptClass->addInternalCall("Internal_SetValue", &ScriptGUISliderField::internal_setValue);
+		metaData.scriptClass->addInternalCall("Internal_HasInputFocus", &ScriptGUISliderField::internal_hasInputFocus);
+		metaData.scriptClass->addInternalCall("Internal_SetTint", &ScriptGUISliderField::internal_setTint);
+		metaData.scriptClass->addInternalCall("Internal_SetRange", &ScriptGUISliderField::internal_setRange);
+		metaData.scriptClass->addInternalCall("Internal_SetStep", &ScriptGUISliderField::internal_setStep);
+
+		onChangedThunk = (OnChangedThunkDef)metaData.scriptClass->getMethod("DoOnChanged", 1)->getThunk();
+	}
+
+	void ScriptGUISliderField::internal_createInstance(MonoObject* instance, float min, float max, MonoObject* title, UINT32 titleWidth,
+		MonoString* style, MonoArray* guiOptions, bool withTitle)
+	{
+		GUIOptions options;
+
+		UINT32 arrayLen = (UINT32)mono_array_length(guiOptions);
+		for (UINT32 i = 0; i < arrayLen; i++)
+			options.addOption(mono_array_get(guiOptions, GUIOption, i));
+
+		String styleName = toString(MonoUtil::monoToWString(style));
+
+		GUISliderField* guiSliderField = nullptr;
+		if (withTitle)
+		{
+			GUIContent nativeContent(ScriptGUIContent::getText(title), ScriptGUIContent::getImage(title), ScriptGUIContent::getTooltip(title));
+			guiSliderField = GUISliderField::create(nativeContent, titleWidth, options, styleName);
+		}
+		else
+		{
+			guiSliderField = GUISliderField::create(options, styleName);
+		}
+
+		guiSliderField->setRange(min, max);
+		guiSliderField->onValueChanged.connect(std::bind(&ScriptGUISliderField::onChanged, instance, _1));
+
+		ScriptGUISliderField* nativeInstance = new (bs_alloc<ScriptGUISliderField>()) ScriptGUISliderField(instance, guiSliderField);
+	}
+
+	float ScriptGUISliderField::internal_getValue(ScriptGUISliderField* nativeInstance)
+	{
+		GUISliderField* sliderField = static_cast<GUISliderField*>(nativeInstance->getGUIElement());
+		return sliderField->getValue();
+	}
+
+	void ScriptGUISliderField::internal_setValue(ScriptGUISliderField* nativeInstance, float value)
+	{
+		GUISliderField* sliderField = static_cast<GUISliderField*>(nativeInstance->getGUIElement());
+		return sliderField->setValue(value);
+	}
+
+	void ScriptGUISliderField::internal_hasInputFocus(ScriptGUISliderField* nativeInstance, bool* output)
+	{
+		GUISliderField* sliderField = static_cast<GUISliderField*>(nativeInstance->getGUIElement());
+		*output = sliderField->hasInputFocus();
+	}
+
+	void ScriptGUISliderField::internal_setTint(ScriptGUISliderField* nativeInstance, Color color)
+	{
+		GUISliderField* sliderField = (GUISliderField*)nativeInstance->getGUIElement();
+		sliderField->setTint(color);
+	}
+
+	void ScriptGUISliderField::internal_setRange(ScriptGUISliderField* nativeInstance, float min, float max)
+	{
+		GUISliderField* sliderField = static_cast<GUISliderField*>(nativeInstance->getGUIElement());
+		sliderField->setRange(min, max);
+	}
+
+	void ScriptGUISliderField::internal_setStep(ScriptGUISliderField* nativeInstance, float step)
+	{
+		GUISliderField* sliderField = static_cast<GUISliderField*>(nativeInstance->getGUIElement());
+		sliderField->setStep(step);
+	}
+
+	void ScriptGUISliderField::onChanged(MonoObject* instance, float newValue)
+	{
+		MonoUtil::invokeThunk(onChangedThunk, instance, newValue);
+	}
+}

+ 8 - 0
SBansheeEngine/Include/BsScriptGUISlider.h

@@ -27,6 +27,10 @@ namespace BansheeEngine
 		static void internal_createInstance(MonoObject* instance, MonoString* style, MonoArray* guiOptions);
 		static void internal_setPercent(ScriptGUISliderH* nativeInstance, float percent);
 		static float internal_getPercent(ScriptGUISliderH* nativeInstance);
+		static float internal_getValue(ScriptGUISliderH* nativeInstance);
+		static void internal_setValue(ScriptGUISliderH* nativeInstance, float percent);
+		static void internal_setRange(ScriptGUISliderH* nativeInstance, float min, float max);
+		static void internal_setStep(ScriptGUISliderH* nativeInstance, float step);
 		static void internal_setTint(ScriptGUISliderH* nativeInstance, Color color);
 
 		typedef void(__stdcall *OnChangedThunkDef) (MonoObject*, float, MonoException**);
@@ -55,6 +59,10 @@ namespace BansheeEngine
 		static void internal_createInstance(MonoObject* instance, MonoString* style, MonoArray* guiOptions);
 		static void internal_setPercent(ScriptGUISliderV* nativeInstance, float percent);
 		static float internal_getPercent(ScriptGUISliderV* nativeInstance);
+		static float internal_getValue(ScriptGUISliderV* nativeInstance);
+		static void internal_setValue(ScriptGUISliderV* nativeInstance, float percent);
+		static void internal_setRange(ScriptGUISliderV* nativeInstance, float min, float max);
+		static void internal_setStep(ScriptGUISliderV* nativeInstance, float step);
 		static void internal_setTint(ScriptGUISliderV* nativeInstance, Color color);
 
 		typedef void(__stdcall *OnChangedThunkDef) (MonoObject*, float, MonoException**);

+ 56 - 0
SBansheeEngine/Source/BsScriptGUISlider.cpp

@@ -32,6 +32,10 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_SetPercent", &ScriptGUISliderH::internal_setPercent);
 		metaData.scriptClass->addInternalCall("Internal_GetPercent", &ScriptGUISliderH::internal_getPercent);
 		metaData.scriptClass->addInternalCall("Internal_SetTint", &ScriptGUISliderH::internal_setTint);
+		metaData.scriptClass->addInternalCall("Internal_GetValue", &ScriptGUISliderH::internal_getValue);
+		metaData.scriptClass->addInternalCall("Internal_SetValue", &ScriptGUISliderH::internal_setValue);
+		metaData.scriptClass->addInternalCall("Internal_SetRange", &ScriptGUISliderH::internal_setRange);
+		metaData.scriptClass->addInternalCall("Internal_SetStep", &ScriptGUISliderH::internal_setStep);
 
 		onChangedThunk = (OnChangedThunkDef)metaData.scriptClass->getMethod("DoOnChanged", 1)->getThunk();
 	}
@@ -62,6 +66,30 @@ namespace BansheeEngine
 		return slider->getPercent();
 	}
 
+	float ScriptGUISliderH::internal_getValue(ScriptGUISliderH* nativeInstance)
+	{
+		GUISliderHorz* slider = (GUISliderHorz*)nativeInstance->getGUIElement();
+		return slider->getValue();
+	}
+
+	void ScriptGUISliderH::internal_setValue(ScriptGUISliderH* nativeInstance, float percent)
+	{
+		GUISliderHorz* slider = (GUISliderHorz*)nativeInstance->getGUIElement();
+		return slider->setValue(percent);
+	}
+
+	void ScriptGUISliderH::internal_setRange(ScriptGUISliderH* nativeInstance, float min, float max)
+	{
+		GUISliderHorz* slider = (GUISliderHorz*)nativeInstance->getGUIElement();
+		return slider->setRange(min, max);
+	}
+
+	void ScriptGUISliderH::internal_setStep(ScriptGUISliderH* nativeInstance, float step)
+	{
+		GUISliderHorz* slider = (GUISliderHorz*)nativeInstance->getGUIElement();
+		return slider->setStep(step);
+	}
+
 	void ScriptGUISliderH::internal_setTint(ScriptGUISliderH* nativeInstance, Color color)
 	{
 		GUISliderHorz* slider = (GUISliderHorz*)nativeInstance->getGUIElement();
@@ -87,6 +115,10 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_SetPercent", &ScriptGUISliderV::internal_setPercent);
 		metaData.scriptClass->addInternalCall("Internal_GetPercent", &ScriptGUISliderV::internal_getPercent);
 		metaData.scriptClass->addInternalCall("Internal_SetTint", &ScriptGUISliderV::internal_setTint);
+		metaData.scriptClass->addInternalCall("Internal_GetValue", &ScriptGUISliderV::internal_getValue);
+		metaData.scriptClass->addInternalCall("Internal_SetValue", &ScriptGUISliderV::internal_setValue);
+		metaData.scriptClass->addInternalCall("Internal_SetRange", &ScriptGUISliderV::internal_setRange);
+		metaData.scriptClass->addInternalCall("Internal_SetStep", &ScriptGUISliderV::internal_setStep);
 
 		onChangedThunk = (OnChangedThunkDef)metaData.scriptClass->getMethod("DoOnChanged", 1)->getThunk();
 	}
@@ -117,6 +149,30 @@ namespace BansheeEngine
 		return slider->getPercent();
 	}
 
+	float ScriptGUISliderV::internal_getValue(ScriptGUISliderV* nativeInstance)
+	{
+		GUISliderVert* slider = (GUISliderVert*)nativeInstance->getGUIElement();
+		return slider->getValue();
+	}
+
+	void ScriptGUISliderV::internal_setValue(ScriptGUISliderV* nativeInstance, float percent)
+	{
+		GUISliderVert* slider = (GUISliderVert*)nativeInstance->getGUIElement();
+		return slider->setValue(percent);
+	}
+
+	void ScriptGUISliderV::internal_setRange(ScriptGUISliderV* nativeInstance, float min, float max)
+	{
+		GUISliderVert* slider = (GUISliderVert*)nativeInstance->getGUIElement();
+		return slider->setRange(min, max);
+	}
+
+	void ScriptGUISliderV::internal_setStep(ScriptGUISliderV* nativeInstance, float step)
+	{
+		GUISliderVert* slider = (GUISliderVert*)nativeInstance->getGUIElement();
+		return slider->setStep(step);
+	}
+
 	void ScriptGUISliderV::internal_setTint(ScriptGUISliderV* nativeInstance, Color color)
 	{
 		GUISliderVert* slider = (GUISliderVert*)nativeInstance->getGUIElement();

+ 13 - 10
TODO.txt

@@ -54,17 +54,16 @@ Polish
 
 Ribek use:
  - Hook up color picker to guicolor field
+ - Component inspectors for: Camera, Renderable, Point/Spot/Directional lights
+ - Resource inspectors for: Material, Texture, Mesh, Font, Shader, Script Code, Plain Text, Sprite Texture, GUISkin, StringTable, Prefab (just something basic for now)
  - Test release mode
+ - Order top-level menu entries according to priority and set valid priorities
 
 Other polish:
- - Component inspectors for: Camera, Renderable, Point/Spot/Directional lights
- - Resource inspectors for: Material, Texture, Mesh, Font, Shader, Script Code, Plain Text, Sprite Texture, GUISkin, StringTable, Prefab (render something similar to SceneObject inspector?)
  - C# interface for Font and SpriteTexture
  - Add menu items:
-  - Edit: Undo/Redo, Cut/Copy/Paste/Duplicate/Delete(need to make sure it works in Hierarchy, with shortcuts), Frame Selected, Preferences, Play/Pause/Step, View/Move/rotate/scale
+  - Edit: Cut/Copy/Paste/Duplicate/Delete(need to make sure it works in Hierarchy, with shortcuts), View/Move/rotate/scale
   - Game Object (also add to context): Create(Empty, Empty Child, Camera, Renderable, Point/Spot/Directional Light), Apply prefab, Break prefab, Revert prefab
-   - Also create helper objects: Cube, Sphere, Plane, Quad, Capsule, Cylinder
-  - Help - About, API Reference (link to site)
  - Add temporary icon textures too all icon buttons currently containing only text so that Ribek can modify them
   - Also add dummy icons to toolbar (New Project, Open Project, Save Scene, Undo, Redo, Basic shapes, Camera, Renderable, Lights)
  - When I expand inspector elements and them come back to that object it should remember the previous state
@@ -76,32 +75,36 @@ Other polish:
 Stage 2 polish:
  - Prefabs
  - Game window
- - Game play/pause/step (+ save/restore objects on play/pause switch)
+ - Game play/pause/step (+ save/restore objects on play/pause switch) (+ toolbar and menu play/pause/step entries)
  - Resource hotswap
  - C# script compiling in editor
  - VS integration
  - When managed exception happens log an error and continue execution
   - Doing a pass over all methods referencing Internal_ methods ensuring they do proper checking on C# side would be good
  - Game publishing (Build window, collect resources, output exe, default viewport) (described below)
+ - Splash screen
+ - Settings/Preferences window (+ menu entry)
+ - Console window
+ - About box - license info and other general info (+ menu entry)
 
 Optional:
  - When starting drag from hierarchy tree view it tends to select another object (can't repro)
  - Handle seems to lag behind the selected mesh
  - When resizing library window while docked, selection area appears
  - Move all the code files into subfolders so their hierarchy is similar to VS filters
- - Splash screen
- - Settings/Preferences window
- - Console window
  - GUI tabbing to switch between elements
+ - Better Prefab inspector - display SceneObject inspector of top-level object, and possibly prefab hierarchy?
+ - GUI element that shows a multi-select drop down + an editor for Layers used in Camera and Renderable
  - Undo/Redo
   - CmdRecordSO records an SO and all its children but it should only record a single SO
   - CmdRecordSO should instead of recording the entire object record a diff
   - There should be a CmdRecordSO equivalent for resources (probably)
   - Add commands for breaking or reverting a scene object 
   - Test & finalize undo/redo system
+  - Add Undo/Redo menu and toolbar entries to "Edit" menu
  - Update GUISlider so has min/max limits, plus step size
   - Add Range[] attribute to C# that forces a float/int to be displayed as a slider
- - Add "focus on object" key (F) - animate it: rotate camera towards then speed towards while zooming in
+ - Add "focus on object" key (F) - animate it: rotate camera towards then speed towards while zooming in (+ menu entry)
  - Ortographic camera views (+ gizmo in scene view corner that shows camera orientation)
  - Drag to select in scene view
  - MenuBar - will likely need a way to mark elements as disabled when not appropriate (e.g. no "frame selected unless scene is focused")