Browse Source

Review select control, make options compatible with 'data-for' view. Add node handler for select which moves the options early before data-for initializes.

Michael Ragazzon 4 years ago
parent
commit
8020239d7d

+ 2 - 2
CMake/FileList.cmake

@@ -45,6 +45,7 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/WidgetTextInputSingleLine.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/WidgetTextInputSingleLine.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/WidgetTextInputSingleLinePassword.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/WidgetTextInputSingleLinePassword.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerDataGrid.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerDataGrid.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerSelect.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerTabSet.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerTabSet.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerTextArea.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerTextArea.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementStyle.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementStyle.h
@@ -166,7 +167,6 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Elements/ElementFormControlTextArea.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Elements/ElementFormControlTextArea.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Elements/ElementProgressBar.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Elements/ElementProgressBar.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Elements/ElementTabSet.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Elements/ElementTabSet.h
-    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Elements/SelectOption.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementScroll.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementScroll.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementText.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementText.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementUtilities.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementUtilities.h
@@ -301,7 +301,6 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/InputTypeRange.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/InputTypeRange.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/InputTypeSubmit.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/InputTypeSubmit.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/InputTypeText.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/InputTypeText.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/Elements/SelectOption.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/WidgetDropDown.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/WidgetDropDown.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/WidgetSlider.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/WidgetSlider.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/WidgetTextInput.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/WidgetTextInput.cpp
@@ -309,6 +308,7 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/WidgetTextInputSingleLine.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/WidgetTextInputSingleLine.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/WidgetTextInputSingleLinePassword.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/WidgetTextInputSingleLinePassword.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerDataGrid.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerDataGrid.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerSelect.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerTabSet.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerTabSet.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerTextArea.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerTextArea.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementScroll.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementScroll.cpp

+ 0 - 1
Include/RmlUi/Core.h

@@ -104,6 +104,5 @@
 #include "Core/Elements/ElementFormControlTextArea.h"
 #include "Core/Elements/ElementFormControlTextArea.h"
 #include "Core/Elements/ElementProgressBar.h"
 #include "Core/Elements/ElementProgressBar.h"
 #include "Core/Elements/ElementTabSet.h"
 #include "Core/Elements/ElementTabSet.h"
-#include "Core/Elements/SelectOption.h"
 
 
 #endif
 #endif

+ 14 - 4
Include/RmlUi/Core/Elements/ElementFormControlSelect.h

@@ -31,7 +31,6 @@
 
 
 #include "../Header.h"
 #include "../Header.h"
 #include "ElementFormControl.h"
 #include "ElementFormControl.h"
-#include "SelectOption.h"
 
 
 namespace Rml {
 namespace Rml {
 
 
@@ -69,9 +68,9 @@ public:
 	int GetSelection() const;
 	int GetSelection() const;
 
 
 	/// Returns one of the select control's option elements.
 	/// Returns one of the select control's option elements.
-	/// @param[in] The index of the desired option element.
-	/// @return The option element at the given index. This will be nullptr if the index is out of bounds.
-	SelectOption* GetOption(int index);
+	/// @param[in] The index of the desired option.
+	/// @return The option element or nullptr if the index was out of bounds.
+	Element* GetOption(int index);
 	/// Returns the number of options in the select control.
 	/// Returns the number of options in the select control.
 	/// @return The number of options.
 	/// @return The number of options.
 	int GetNumOptions();
 	int GetNumOptions();
@@ -83,6 +82,11 @@ public:
 	/// @param[in] selectable If true this option can be selected. If false, this option is not selectable.
 	/// @param[in] selectable If true this option can be selected. If false, this option is not selectable.
 	/// @return The index of the new option.
 	/// @return The index of the new option.
 	int Add(const String& rml, const String& value, int before = -1, bool selectable = true);
 	int Add(const String& rml, const String& value, int before = -1, bool selectable = true);
+	/// Adds a new option to the select control.
+	/// @param[in] element The element to add, must be an 'option' element.
+	/// @param[in] before The index of the element to insert the new option before.
+	/// @return The index of the new option.
+	int Add(ElementPtr element, int before = -1);
 	/// Removes an option from the select control.
 	/// Removes an option from the select control.
 	/// @param[in] index The index of the option to remove. If this is outside of the bounds of the control's option list, no option will be removed.
 	/// @param[in] index The index of the option to remove. If this is outside of the bounds of the control's option list, no option will be removed.
 	void Remove(int index);
 	void Remove(int index);
@@ -99,6 +103,10 @@ protected:
 	/// Forces an internal layout.
 	/// Forces an internal layout.
 	void OnLayout() override;
 	void OnLayout() override;
 
 
+	void OnChildAdd(Element* child) override;
+	
+	void OnChildRemove(Element* child) override;
+
 	/// Returns true to mark this element as replaced.
 	/// Returns true to mark this element as replaced.
 	/// @param[out] intrinsic_dimensions Set to the arbitrary dimensions of 128 x 16 just to give this element a size. Resize with the 'width' and 'height' properties.
 	/// @param[out] intrinsic_dimensions Set to the arbitrary dimensions of 128 x 16 just to give this element a size. Resize with the 'width' and 'height' properties.
 	/// @param[out] intrinsic_ratio Ignored.
 	/// @param[out] intrinsic_ratio Ignored.
@@ -108,6 +116,8 @@ protected:
 	/// Respond to changed value attribute.
 	/// Respond to changed value attribute.
 	void OnAttributeChange(const ElementAttributes& changed_attributes) override;
 	void OnAttributeChange(const ElementAttributes& changed_attributes) override;
 
 
+	void MoveChildren();
+
 	WidgetDropDown* widget;
 	WidgetDropDown* widget;
 };
 };
 
 

+ 0 - 70
Include/RmlUi/Core/Elements/SelectOption.h

@@ -1,70 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#ifndef RMLUI_CORE_ELEMENTS_SELECTOPTION_H
-#define RMLUI_CORE_ELEMENTS_SELECTOPTION_H
-
-#include "../Header.h"
-#include "../Types.h"
-
-namespace Rml {
-
-class Element;
-
-
-/**
-	Represents individual options within a select control.
-
-	@author Peter Curry
- */
-
-class RMLUICORE_API SelectOption
-{
-public:
-	SelectOption(Element* element, const String& value, bool selectable);
-	~SelectOption();
-
-	/// Returns the element that represents the option visually.
-	/// @return The option's element.
-	Element* GetElement();
-	/// Returns the value of the option.
-	/// @return The option's value.
-	const String& GetValue() const;
-
-	/// Returns true if the item is selectable.
-	/// @return True if the item is selectable.
-	bool IsSelectable() { return selectable; }
-
-private:
-	Element* element;
-	String value;
-	bool selectable;
-};
-
-} // namespace Rml
-#endif

+ 22 - 30
Samples/basic/databinding/data/databinding.rml

@@ -150,9 +150,12 @@ form h2 {
 	background-color: #aca;
 	background-color: #aca;
 	color: #999;
 	color: #999;
 }
 }
-.picker p {
+a {
 	cursor: pointer;
 	cursor: pointer;
 }
 }
+a:hover {
+	text-decoration: underline;
+}
 input:disabled {
 input:disabled {
 	image-color: #aaac;
 	image-color: #aaac;
 }
 }
@@ -163,6 +166,10 @@ li {
 	margin-top: 0.3em;
 	margin-top: 0.3em;
 	margin-bottom: 0.3em;
 	margin-bottom: 0.3em;
 }
 }
+select selectbox option.disabled {
+	color: #666;
+	background-color: #ccc;
+}
 </style>
 </style>
 </head>
 </head>
 
 
@@ -216,15 +223,6 @@ li {
 <tab>Forms</tab>
 <tab>Forms</tab>
 <panel id="controls" data-model="forms">
 <panel id="controls" data-model="forms">
 	<form onsubmit="submit_form">
 	<form onsubmit="submit_form">
-		<h2>Full name</h2>
-		<div>
-			<input class="two-wide" type="text" name="name"/>
-		</div>
-		<h2>Email and password</h2>
-		<div>
-			<input type="text" name="email"/>
-			<input type="password" name="password"/>
-		</div>
 		<h2>Rating</h2>
 		<h2>Rating</h2>
 		<div>
 		<div>
 			<input type="range" name="rating" min="0" max="100" step="1" value="50" onchange="rating" data-value="rating"/> <span id="rating">{{rating}}</span><span id="rating_emoji">&nbsp;</span>
 			<input type="range" name="rating" min="0" max="100" step="1" value="50" onchange="rating" data-value="rating"/> <span id="rating">{{rating}}</span><span id="rating_emoji">&nbsp;</span>
@@ -248,30 +246,24 @@ li {
 		</div>
 		</div>
 		<h2>Subject</h2>
 		<h2>Subject</h2>
 		<div>
 		<div>
-			<!--<select name="subject" data-value="selected_subject">
-                <option data-for="s : subjects" data-value="it_index">{{s}}</option>
-            </select>-->
-            <select name="subject" data-value="selected_subject">
-                <option value="0">Choose your subject</option>
-                <option value="1">Feature request</option>
-                <option value="2">Bug report</option>
-                <option value="3">Praise</option>
-                <option value="4">Criticism</option>
+			<select id="select" name="subject" data-value="selected_subject">
+                <option data-for="s : subjects" data-value="it_index" data-class-disabled="it_index == 0" data-attrif-disabled="it_index == 0">{{s}}</option>
             </select>
             </select>
-			<div class="picker">
-				<p data-for="s : subjects" data-style-color="it_index == selected_subject ? 'red' : 'black'" data-event-click="selected_subject = it_index">{{it_index + 1 + ': ' + s}}</p>
-			</div>
+			<br/>
 			<p>
 			<p>
-				Selected subject index:<br/>
+				Select subject by index<br/>
 				<input type="range" name="rating" min="0" max="0" data-attr-max="subjects.size - 1" step="1" value="0" data-value="selected_subject"/> {{selected_subject + 1}}
 				<input type="range" name="rating" min="0" max="0" data-attr-max="subjects.size - 1" step="1" value="0" data-value="selected_subject"/> {{selected_subject + 1}}
 			</p>
 			</p>
-		</div>
-		<h2>Message</h2>
-		<div>
-			<textarea cols="25" rows="4" wrap="nowrap" name="message">😍 Hello 🌐 World! 😎</textarea>
-		</div>
-		<div style="margin-bottom: 15dp;">
-			<input type="submit">Submit</input>
+			<div class="picker">
+				<p data-for="s : subjects" data-style-color="it_index == selected_subject ? 'red' : 'black'">
+					{{it_index + 1}}: <a data-event-click="selected_subject = it_index">{{ s }}</a>&nbsp;
+					<a data-if="it_index > 0" data-event-click="erase_subject(it_index)">[-]</a>
+				</p>
+			</div>
+			<div>
+				<input type="text" name="name" data-value="new_subject"/><br/>
+				<button data-event-click="add_subject(new_subject)">Add subject</button>
+			</div>
 		</div>
 		</div>
 	</form>
 	</form>
 </panel>
 </panel>

+ 20 - 0
Samples/basic/databinding/src/main.cpp

@@ -286,6 +286,7 @@ namespace FormsExample {
 		Rml::String animal = "dog";
 		Rml::String animal = "dog";
 		Rml::Vector<Rml::String> subjects = { "Choose your subject", "Feature request", "Bug report", "Praise", "Criticism" };
 		Rml::Vector<Rml::String> subjects = { "Choose your subject", "Feature request", "Bug report", "Praise", "Criticism" };
 		int selected_subject = 0;
 		int selected_subject = 0;
+		Rml::String new_subject = "New subject";
 	} my_data;
 	} my_data;
 
 
 	bool Initialize(Rml::Context* context)
 	bool Initialize(Rml::Context* context)
@@ -303,6 +304,25 @@ namespace FormsExample {
 		constructor.Bind("animal", &my_data.animal);
 		constructor.Bind("animal", &my_data.animal);
 		constructor.Bind("subjects", &my_data.subjects);
 		constructor.Bind("subjects", &my_data.subjects);
 		constructor.Bind("selected_subject", &my_data.selected_subject);
 		constructor.Bind("selected_subject", &my_data.selected_subject);
+		constructor.Bind("new_subject", &my_data.new_subject);
+
+		constructor.BindEventCallback("add_subject", [](Rml::DataModelHandle model, Rml::Event& /*ev*/, const Rml::VariantList& arguments) {
+			Rml::String name = (arguments.size() == 1 ? arguments[0].Get<Rml::String>() : "");
+			if (!name.empty()) {
+				my_data.subjects.push_back(std::move(name));
+				model.DirtyVariable("subjects");
+			}
+		});
+		constructor.BindEventCallback("erase_subject", [](Rml::DataModelHandle model, Rml::Event& /*ev*/, const Rml::VariantList& arguments) {
+			const int i = (arguments.size() == 1 ? arguments[0].Get<int>(-1) : -1);
+			if (i >= 0 && i < (int)my_data.subjects.size())
+			{
+				my_data.subjects.erase(my_data.subjects.begin() + i);
+				my_data.selected_subject = 0;
+				model.DirtyVariable("subjects");
+				model.DirtyVariable("selected_subject");
+			}
+		});
 
 
 		model_handle = constructor.GetModelHandle();
 		model_handle = constructor.GetModelHandle();
 
 

+ 5 - 4
Source/Core/Elements/ElementFormControlDataSelect.cpp

@@ -216,10 +216,11 @@ void ElementFormControlDataSelect::BuildOptions()
 		// Try to find a selection with the same value as the previous one.
 		// Try to find a selection with the same value as the previous one.
 		for (int i = 0; i < GetNumOptions(); ++i)
 		for (int i = 0; i < GetNumOptions(); ++i)
 		{
 		{
-			SelectOption* option = GetOption(i);
-			if (option->GetValue() == old_value)
+			Element* option = GetOption(i);
+			Variant* variant = option->GetAttribute("value");
+			if (variant && variant->Get<String>() == old_value)
 			{
 			{
-				widget->SetSelection(i, true);
+				widget->SetSelection(option, true);
 				return;
 				return;
 			}
 			}
 		}
 		}
@@ -229,7 +230,7 @@ void ElementFormControlDataSelect::BuildOptions()
 		if (GetNumOptions() == 0)
 		if (GetNumOptions() == 0)
 			new_selection = -1;
 			new_selection = -1;
 
 
-		widget->SetSelection(new_selection, true);
+		widget->SetSelection(GetOption(new_selection), true);
 	}
 	}
 }
 }
 
 

+ 46 - 36
Source/Core/Elements/ElementFormControlSelect.cpp

@@ -35,7 +35,7 @@
 namespace Rml {
 namespace Rml {
 
 
 // Constructs a new ElementFormControlSelect.
 // Constructs a new ElementFormControlSelect.
-ElementFormControlSelect::ElementFormControlSelect(const String& tag) : ElementFormControl(tag)
+ElementFormControlSelect::ElementFormControlSelect(const String& tag) : ElementFormControl(tag), widget(nullptr)
 {
 {
 	widget = new WidgetDropDown(this);
 	widget = new WidgetDropDown(this);
 }
 }
@@ -48,77 +48,75 @@ ElementFormControlSelect::~ElementFormControlSelect()
 // Returns a string representation of the current value of the form control.
 // Returns a string representation of the current value of the form control.
 String ElementFormControlSelect::GetValue() const
 String ElementFormControlSelect::GetValue() const
 {
 {
-	RMLUI_ASSERT(widget != nullptr);
-	return widget->GetValue();
+	return GetAttribute("value", String());
 }
 }
 
 
 // Sets the current value of the form control.
 // Sets the current value of the form control.
 void ElementFormControlSelect::SetValue(const String& value)
 void ElementFormControlSelect::SetValue(const String& value)
 {
 {
-	OnUpdate();
+	MoveChildren();
 
 
-	RMLUI_ASSERT(widget != nullptr);
-	widget->SetValue(value);
+	SetAttribute("value", value);
 }
 }
 
 
 // Sets the index of the selection. If the new index lies outside of the bounds, it will be clamped.
 // Sets the index of the selection. If the new index lies outside of the bounds, it will be clamped.
 void ElementFormControlSelect::SetSelection(int selection)
 void ElementFormControlSelect::SetSelection(int selection)
 {
 {
-	OnUpdate();
+	MoveChildren();
 
 
-	RMLUI_ASSERT(widget != nullptr);
-	widget->SetSelection(selection);
+	widget->SetSelection(widget->GetOption(selection));
 }
 }
 
 
 // Returns the index of the currently selected item.
 // Returns the index of the currently selected item.
 int ElementFormControlSelect::GetSelection() const
 int ElementFormControlSelect::GetSelection() const
 {
 {
-	RMLUI_ASSERT(widget != nullptr);
 	return widget->GetSelection();
 	return widget->GetSelection();
 }
 }
 
 
 // Returns one of the select control's option elements.
 // Returns one of the select control's option elements.
-SelectOption* ElementFormControlSelect::GetOption(int index)
+Element* ElementFormControlSelect::GetOption(int index)
 {
 {
-	OnUpdate();
+	MoveChildren();
 
 
-	RMLUI_ASSERT(widget != nullptr);
 	return widget->GetOption(index);
 	return widget->GetOption(index);
 }
 }
 
 
 // Returns the number of options in the select control.
 // Returns the number of options in the select control.
 int ElementFormControlSelect::GetNumOptions()
 int ElementFormControlSelect::GetNumOptions()
 {
 {
-	OnUpdate();
+	MoveChildren();
 
 
-	RMLUI_ASSERT(widget != nullptr);
 	return widget->GetNumOptions();
 	return widget->GetNumOptions();
 }
 }
 
 
 // Adds a new option to the select control.
 // Adds a new option to the select control.
 int ElementFormControlSelect::Add(const String& rml, const String& value, int before, bool selectable)
 int ElementFormControlSelect::Add(const String& rml, const String& value, int before, bool selectable)
 {
 {
-	OnUpdate();
+	MoveChildren();
 
 
-	RMLUI_ASSERT(widget != nullptr);
 	return widget->AddOption(rml, value, before, false, selectable);
 	return widget->AddOption(rml, value, before, false, selectable);
 }
 }
 
 
+int ElementFormControlSelect::Add(ElementPtr element, int before)
+{
+	MoveChildren();
+
+	return widget->AddOption(std::move(element), before);
+}
+
 // Removes an option from the select control.
 // Removes an option from the select control.
 void ElementFormControlSelect::Remove(int index)
 void ElementFormControlSelect::Remove(int index)
 {
 {
-	OnUpdate();
+	MoveChildren();
 
 
-	RMLUI_ASSERT(widget != nullptr);
 	widget->RemoveOption(index);
 	widget->RemoveOption(index);
 }
 }
 
 
 // Removes all options from the select control.
 // Removes all options from the select control.
 void ElementFormControlSelect::RemoveAll()
 void ElementFormControlSelect::RemoveAll()
 {
 {
-	OnUpdate();
+	MoveChildren();
 
 
-	RMLUI_ASSERT(widget != nullptr);
 	widget->ClearOptions();
 	widget->ClearOptions();
 }
 }
 
 
@@ -127,21 +125,9 @@ void ElementFormControlSelect::OnUpdate()
 {
 {
 	ElementFormControl::OnUpdate();
 	ElementFormControl::OnUpdate();
 
 
-	// Move any child elements into the widget (except for the three functional elements).
-	while (Element * raw_child = GetFirstChild())
-	{
-		ElementPtr child = RemoveChild(raw_child);
-
-		bool select = child->GetAttribute("selected");
-		bool selectable = !child->GetAttribute("disabled");
-		String option_value = child->GetAttribute("value", String());
-
-		child->RemoveAttribute("selected");
-		child->RemoveAttribute("disabled");
-		child->RemoveAttribute("value");
+	MoveChildren();
 
 
-		widget->AddOption(std::move(child), option_value, -1, select, selectable);
-	}
+	widget->OnUpdate();
 }
 }
 
 
 // Updates the layout of the widget's elements.
 // Updates the layout of the widget's elements.
@@ -158,6 +144,28 @@ void ElementFormControlSelect::OnLayout()
 	widget->OnLayout();
 	widget->OnLayout();
 }
 }
 
 
+void ElementFormControlSelect::OnChildAdd(Element* child)
+{
+	if (widget)
+		widget->OnChildAdd(child);
+}
+
+void ElementFormControlSelect::OnChildRemove(Element* child)
+{
+	if (widget)
+		widget->OnChildRemove(child);
+}
+
+void ElementFormControlSelect::MoveChildren()
+{
+	// Move any child elements into the widget (except for the three functional elements).
+	while (Element* raw_child = GetFirstChild())
+	{
+		ElementPtr child = RemoveChild(raw_child);
+		widget->AddOption(std::move(child), -1);
+	}
+}
+
 // Returns true to mark this element as replaced.
 // Returns true to mark this element as replaced.
 bool ElementFormControlSelect::GetIntrinsicDimensions(Vector2f& intrinsic_dimensions, float& /*ratio*/)
 bool ElementFormControlSelect::GetIntrinsicDimensions(Vector2f& intrinsic_dimensions, float& /*ratio*/)
 {
 {
@@ -168,11 +176,13 @@ bool ElementFormControlSelect::GetIntrinsicDimensions(Vector2f& intrinsic_dimens
 
 
 void ElementFormControlSelect::OnAttributeChange(const ElementAttributes& changed_attributes)
 void ElementFormControlSelect::OnAttributeChange(const ElementAttributes& changed_attributes)
 {
 {
+	RMLUI_ASSERT(widget);
+
 	ElementFormControl::OnAttributeChange(changed_attributes);
 	ElementFormControl::OnAttributeChange(changed_attributes);
 
 
 	auto it = changed_attributes.find("value");
 	auto it = changed_attributes.find("value");
 	if (it != changed_attributes.end())
 	if (it != changed_attributes.end())
-		SetValue(it->second.Get<String>());
+		widget->OnValueChange(it->second.Get<String>());
 }
 }
 
 
 } // namespace Rml
 } // namespace Rml

+ 182 - 98
Source/Core/Elements/WidgetDropDown.cpp

@@ -44,12 +44,12 @@ WidgetDropDown::WidgetDropDown(ElementFormControl* element)
 {
 {
 	parent_element = element;
 	parent_element = element;
 
 
+	selection_dirty = false;
 	box_layout_dirty = false;
 	box_layout_dirty = false;
+	value_rml_dirty = false;
 	value_layout_dirty = false;
 	value_layout_dirty = false;
 	box_visible = false;
 	box_visible = false;
 
 
-	selected_option = -1;
-
 	// Create the button and selection elements.
 	// Create the button and selection elements.
 	button_element = parent_element->AppendChild(Factory::InstanceElement(parent_element, "*", "selectarrow", XMLAttributes()), false);
 	button_element = parent_element->AppendChild(Factory::InstanceElement(parent_element, "*", "selectarrow", XMLAttributes()), false);
 	value_element = parent_element->AppendChild(Factory::InstanceElement(parent_element, "*", "selectvalue", XMLAttributes()), false);
 	value_element = parent_element->AppendChild(Factory::InstanceElement(parent_element, "*", "selectvalue", XMLAttributes()), false);
@@ -75,8 +75,9 @@ WidgetDropDown::~WidgetDropDown()
 {
 {
 	// We shouldn't clear the options ourselves, as removing the element will automatically clear children.
 	// We shouldn't clear the options ourselves, as removing the element will automatically clear children.
 	//   However, we do need to remove events of children.
 	//   However, we do need to remove events of children.
-	for(auto& option : options)
-		option.GetElement()->RemoveEventListener(EventId::Click, this);
+	const int num_options = selection_element->GetNumChildren();
+	for (int i = 0; i < num_options; i++)
+		selection_element->GetChild(i)->RemoveEventListener(EventId::Click, this);
 
 
 	parent_element->RemoveEventListener(EventId::Click, this, true);
 	parent_element->RemoveEventListener(EventId::Click, this, true);
 	parent_element->RemoveEventListener(EventId::Blur, this);
 	parent_element->RemoveEventListener(EventId::Blur, this);
@@ -88,7 +89,56 @@ WidgetDropDown::~WidgetDropDown()
 	DetachScrollEvent();
 	DetachScrollEvent();
 }
 }
 
 
-// Updates the selection box layout if necessary.
+void WidgetDropDown::OnUpdate()
+{
+	if (selection_dirty)
+	{
+		// Find the best option element to select in the following priority:
+		//  1. First option with 'selected' attribute.
+		//  2. An option whose 'value' attribute matches the select 'value' attribute.
+		//  3. The first option.
+		// The select element's value may change as a result of this.
+		const String select_value = parent_element->GetAttribute("value", String());
+		Element* select_option = selection_element->GetFirstChild();
+
+		const int num_options = selection_element->GetNumChildren();
+		for (int i = 0; i < num_options; i++)
+		{
+			Element* option = selection_element->GetChild(i);
+			if (option->HasAttribute("selected"))
+			{
+				select_option = option;
+				break;
+			}
+			else if (!select_value.empty() && select_value == option->GetAttribute("value", String()))
+			{
+				select_option = option;
+			}
+		}
+
+		if (select_option)
+			SetSelection(select_option);
+
+		selection_dirty = false;
+	}
+
+	if (value_rml_dirty)
+	{
+		String value_rml;
+		const int selection = GetSelection();
+
+		if (Element* option = selection_element->GetChild(selection))
+			option->GetInnerRML(value_rml);
+		else
+			value_rml = parent_element->GetValue();
+
+		value_element->SetInnerRML(value_rml);
+
+		value_rml_dirty = false;
+		value_layout_dirty = true;
+	}
+}
+
 void WidgetDropDown::OnRender()
 void WidgetDropDown::OnRender()
 {
 {
 	if (box_visible && box_layout_dirty)
 	if (box_visible && box_layout_dirty)
@@ -213,166 +263,198 @@ void WidgetDropDown::OnLayout()
 }
 }
 
 
 // Sets the value of the widget.
 // Sets the value of the widget.
-void WidgetDropDown::SetValue(const String& _value)
+void WidgetDropDown::OnValueChange(const String& value)
 {
 {
-	for (size_t i = 0; i < options.size(); ++i)
+	Element* select_option = nullptr;
+	const int num_options = selection_element->GetNumChildren();
+	for (int i = 0; i < num_options; i++)
 	{
 	{
-		if (options[i].GetValue() == _value)
+		Element* option = selection_element->GetChild(i);
+		Variant* variant = option->GetAttribute("value");
+		if (variant && variant->Get<String>() == value)
 		{
 		{
-			SetSelection((int) i);
-			return;
+			select_option = option;
+			break;
 		}
 		}
 	}
 	}
 
 
-	if (selected_option >= 0 && selected_option < (int)options.size())
-		options[selected_option].GetElement()->SetPseudoClass("checked", false);
-
-	value = _value;
-	value_element->SetInnerRML(value);
-	value_layout_dirty = true;
+	if (select_option && !select_option->HasAttribute("selected"))
+		SetSelection(select_option, true);
 
 
-	selected_option = -1;
-}
+	Dictionary parameters;
+	parameters["value"] = value;
+	parent_element->DispatchEvent(EventId::Change, parameters);
 
 
-// Returns the current value of the widget.
-const String& WidgetDropDown::GetValue() const
-{
-	return value;
+	value_rml_dirty = true;
 }
 }
 
 
-// Sets the index of the selection. If the new index lies outside of the bounds, it will be clamped.
-void WidgetDropDown::SetSelection(int selection, bool force)
+void WidgetDropDown::SetSelection(Element* select_option, bool force)
 {
 {
-	String new_value;
+	const String old_value = parent_element->GetAttribute("value", String());
+	const String new_value = select_option ? select_option->GetAttribute("value", String()) : String();
 
 
-	if (selection < 0 ||
-		selection >= (int) options.size())
+	const int num_options = selection_element->GetNumChildren();
+	for (int i = 0; i < num_options; i++)
 	{
 	{
-		selection = -1;
+		Element* option = selection_element->GetChild(i);
+		
+		if (select_option == option)
+		{
+			option->SetAttribute("selected", String());
+			option->SetPseudoClass("checked", true);
+		}
+		else
+		{
+			option->RemoveAttribute("selected");
+			option->SetPseudoClass("checked", false);
+		}
 	}
 	}
-	else
+
+	if (force || (old_value != new_value))
 	{
 	{
-		new_value = options[selection].GetValue();
+		parent_element->SetAttribute("value", new_value);
 	}
 	}
 
 
-	if (force ||
-		selection != selected_option ||
-		value != new_value)
-	{
-		if (selected_option >= 0 && selected_option < (int)options.size())
-			options[selected_option].GetElement()->SetPseudoClass("checked", false);
-		
-		selected_option = selection;
-		value = new_value;
+	value_rml_dirty = true;
+}
 
 
-		String value_rml;
-		if (selected_option >= 0) 
-		{
-			auto* el = options[selected_option].GetElement();
-			el->GetInnerRML(value_rml);
-			el->SetPseudoClass("checked", true);
-		}
+void WidgetDropDown::SeekSelection(bool seek_forward)
+{
+	const int selected_option = GetSelection();
+	const int num_options = selection_element->GetNumChildren();
 
 
+	for (int i = 1; i < num_options && selected_option >= 0; i++)
+	{
+		const int option_index = (selected_option + i * (seek_forward ? 1 : -1) + num_options) % num_options;
 
 
-		value_element->SetInnerRML(value_rml);
-		value_layout_dirty = true;
+		Element* element = selection_element->GetChild(option_index);
 
 
-		Dictionary parameters;
-		parameters["value"] = value;
-		parent_element->DispatchEvent(EventId::Change, parameters);
+		if (!element->HasAttribute("disabled") && element->IsVisible())
+		{
+			SetSelection(element);
+			return;
+		}
 	}
 	}
+
+	// No valid option found, remove any selection.
+	SetSelection(nullptr);
 }
 }
 
 
 // Returns the index of the currently selected item.
 // Returns the index of the currently selected item.
 int WidgetDropDown::GetSelection() const
 int WidgetDropDown::GetSelection() const
 {
 {
-	return selected_option;
+	const int num_options = selection_element->GetNumChildren();
+	for (int i = 0; i < num_options; i++)
+	{
+		if (selection_element->GetChild(i)->HasAttribute("selected"))
+			return i;
+	}
+
+	return -1;
 }
 }
 
 
 // Adds a new option to the select control.
 // Adds a new option to the select control.
-int WidgetDropDown::AddOption(const String& rml, const String& new_value, int before, bool select, bool selectable)
+int WidgetDropDown::AddOption(const String& rml, const String& option_value, int before, bool select, bool selectable)
 {
 {
 	ElementPtr element = Factory::InstanceElement(selection_element, "*", "option", XMLAttributes());
 	ElementPtr element = Factory::InstanceElement(selection_element, "*", "option", XMLAttributes());
 	element->SetInnerRML(rml);
 	element->SetInnerRML(rml);
 
 
-	int result = AddOption(std::move(element), new_value, before, select, selectable);
+	element->SetAttribute("value", option_value);
+
+	if (select)
+		element->SetAttribute("selected", String());
+	if (!selectable)
+		element->SetAttribute("disabled", String());
+
+	int result = AddOption(std::move(element), before);
 
 
 	return result;
 	return result;
 }
 }
 
 
-int WidgetDropDown::AddOption(ElementPtr element, const String& new_value, int before, bool select, bool selectable)
+int WidgetDropDown::AddOption(ElementPtr element, int before)
 {
 {
-	static const String str_option = "option";
-
-	if (element->GetTagName() != str_option)
+	if (element->GetTagName() != "option")
 	{
 	{
 		Log::Message(Log::LT_WARNING, "A child of '%s' must be of type 'option' but '%s' was given. See element '%s'.", parent_element->GetTagName().c_str(), element->GetTagName().c_str(), parent_element->GetAddress().c_str());
 		Log::Message(Log::LT_WARNING, "A child of '%s' must be of type 'option' but '%s' was given. See element '%s'.", parent_element->GetTagName().c_str(), element->GetTagName().c_str(), parent_element->GetAddress().c_str());
 		return -1;
 		return -1;
 	}
 	}
 
 
-	// Force to block display. Register a click handler so we can be notified of selection.
-	element->SetProperty(PropertyId::Display, Property(Style::Display::Block));
-	element->SetProperty(PropertyId::Clip, Property(Style::Clip::Type::Auto));
-	element->AddEventListener(EventId::Click, this);
-
+	const int num_children_before = selection_element->GetNumChildren();
 	int option_index;
 	int option_index;
-	if (before < 0 || before >= (int)options.size())
+	if (before < 0 || before >= num_children_before)
 	{
 	{
-		Element* ptr = selection_element->AppendChild(std::move(element));
-		options.push_back(SelectOption(ptr, new_value, selectable));
-		option_index = (int)options.size() - 1;
+		selection_element->AppendChild(std::move(element));
+		option_index = num_children_before;
 	}
 	}
 	else
 	else
 	{
 	{
-		Element* ptr = selection_element->InsertBefore(std::move(element), selection_element->GetChild(before));
-		options.insert(options.begin() + before, SelectOption(ptr, new_value, selectable));
+		selection_element->InsertBefore(std::move(element), selection_element->GetChild(before));
 		option_index = before;
 		option_index = before;
 	}
 	}
 
 
-	// Select the option if appropriate.
-	if (select)
-		SetSelection(option_index);
-
-	box_layout_dirty = true;
 	return option_index;
 	return option_index;
 }
 }
 
 
 // Removes an option from the select control.
 // Removes an option from the select control.
 void WidgetDropDown::RemoveOption(int index)
 void WidgetDropDown::RemoveOption(int index)
 {
 {
-	if (index < 0 ||
-		index >= (int) options.size())
+	Element* element = selection_element->GetChild(index);
+	if (!element)
 		return;
 		return;
 
 
-	// Remove the listener and delete the option element.
-	options[index].GetElement()->RemoveEventListener(EventId::Click, this);
-	selection_element->RemoveChild(options[index].GetElement());
-	options.erase(options.begin() + index);
-
-	box_layout_dirty = true;
+	selection_element->RemoveChild(element);
 }
 }
 
 
 // Removes all options from the list.
 // Removes all options from the list.
 void WidgetDropDown::ClearOptions()
 void WidgetDropDown::ClearOptions()
 {
 {
-	while (!options.empty())
-		RemoveOption((int) options.size() - 1);
+	while (Element* element = selection_element->GetLastChild())
+		selection_element->RemoveChild(element);
 }
 }
 
 
 // Returns on of the widget's options.
 // Returns on of the widget's options.
-SelectOption* WidgetDropDown::GetOption(int index)
+Element* WidgetDropDown::GetOption(int index)
 {
 {
-	if (index < 0 ||
-		index >= GetNumOptions())
-		return nullptr;
-
-	return &options[index];
+	return selection_element->GetChild(index);
 }
 }
 
 
 // Returns the number of options in the widget.
 // Returns the number of options in the widget.
 int WidgetDropDown::GetNumOptions() const
 int WidgetDropDown::GetNumOptions() const
 {
 {
-	return (int) options.size();
+	return selection_element->GetNumChildren();
+}
+
+void WidgetDropDown::OnChildAdd(Element* element)
+{
+	// We have a special case for 'data-for' here, since that element must remain hidden.
+	if (element->GetParentNode() != selection_element || element->HasAttribute("data-for"))
+		return;
+
+	// Force to block display. Register a click handler so we can be notified of selection.
+	element->SetProperty(PropertyId::Display, Property(Style::Display::Block));
+	element->SetProperty(PropertyId::Clip, Property(Style::Clip::Type::Auto));
+	element->AddEventListener(EventId::Click, this);
+
+	// Select the option if appropriate.
+	if (element->HasAttribute("selected"))
+		SetSelection(element, true);
+
+	selection_dirty = true;
+	box_layout_dirty = true;
+}
+
+void WidgetDropDown::OnChildRemove(Element* element)
+{
+	if (element->GetParentNode() != selection_element)
+		return;
+
+	element->RemoveEventListener(EventId::Click, this);
+
+	if (element->HasAttribute("selected"))
+		SetSelection(nullptr);
+
+	selection_dirty = true;
+	box_layout_dirty = true;
 }
 }
 
 
 void WidgetDropDown::AttachScrollEvent()
 void WidgetDropDown::AttachScrollEvent()
@@ -397,17 +479,18 @@ void WidgetDropDown::ProcessEvent(Event& event)
 	{
 	{
 	case EventId::Click:
 	case EventId::Click:
 	{
 	{
-
 		if (event.GetCurrentElement()->GetParentNode() == selection_element)
 		if (event.GetCurrentElement()->GetParentNode() == selection_element)
 		{
 		{
+			const int num_options = selection_element->GetNumChildren();
 			// Find the element in the options and fire the selection event
 			// Find the element in the options and fire the selection event
-			for (size_t i = 0; i < options.size(); i++)
+			for (int i = 0; i < num_options; i++)
 			{
 			{
-				if (options[i].GetElement() == event.GetCurrentElement())
+				Element* current_element = event.GetCurrentElement();
+				if (selection_element->GetChild(i) == current_element)
 				{
 				{
-					if (options[i].IsSelectable())
+					if (!event.GetCurrentElement()->HasAttribute("disabled"))
 					{
 					{
-						SetSelection((int)i);
+						SetSelection(current_element);
 						event.StopPropagation();
 						event.StopPropagation();
 
 
 						ShowSelectBox(false);
 						ShowSelectBox(false);
@@ -463,11 +546,11 @@ void WidgetDropDown::ProcessEvent(Event& event)
 		switch (key_identifier)
 		switch (key_identifier)
 		{
 		{
 		case Input::KI_UP:
 		case Input::KI_UP:
-			SetSelection((selected_option - 1 + (int)options.size()) % (int)options.size());
+			SeekSelection(false);
 			event.StopPropagation();
 			event.StopPropagation();
 			break;
 			break;
 		case Input::KI_DOWN:
 		case Input::KI_DOWN:
-			SetSelection((selected_option + 1) % (int)options.size());
+			SeekSelection(true);
 			event.StopPropagation();
 			event.StopPropagation();
 			break;
 			break;
 		default:
 		default:
@@ -532,6 +615,7 @@ void WidgetDropDown::ShowSelectBox(bool show)
 		button_element->SetPseudoClass("checked", false);
 		button_element->SetPseudoClass("checked", false);
 		DetachScrollEvent();
 		DetachScrollEvent();
 	}
 	}
+
 	box_visible = show;
 	box_visible = show;
 }
 }
 
 

+ 22 - 28
Source/Core/Elements/WidgetDropDown.h

@@ -30,7 +30,6 @@
 #define RMLUI_CORE_ELEMENTS_WIDGETDROPDOWN_H
 #define RMLUI_CORE_ELEMENTS_WIDGETDROPDOWN_H
 
 
 #include "../../../Include/RmlUi/Core/EventListener.h"
 #include "../../../Include/RmlUi/Core/EventListener.h"
-#include "../../../Include/RmlUi/Core/Elements/SelectOption.h"
 
 
 namespace Rml {
 namespace Rml {
 
 
@@ -47,24 +46,24 @@ public:
 	WidgetDropDown(ElementFormControl* element);
 	WidgetDropDown(ElementFormControl* element);
 	virtual ~WidgetDropDown();
 	virtual ~WidgetDropDown();
 
 
+	/// Updates the select value rml if necessary.
+	void OnUpdate();
 	/// Updates the selection box layout if necessary.
 	/// Updates the selection box layout if necessary.
 	void OnRender();
 	void OnRender();
 	/// Positions the drop-down's internal elements.
 	/// Positions the drop-down's internal elements.
 	void OnLayout();
 	void OnLayout();
 
 
 	/// Sets the value of the widget.
 	/// Sets the value of the widget.
-	/// @param[in] value The new value to set.
-	void SetValue(const String& value);
-	/// Returns the current value of the widget.
-	/// @return The current value of the widget.
-	const String& GetValue() const;
-
-	/// Sets the index of the selection. If the new index lies outside of the bounds, the selection index will be set to -1.
-	/// @param[in] selection The new selection index.
+	void OnValueChange(const String& value);
+
+	/// Sets the option element as the new selection.
+	/// @param[in] option_element The option element to select.
 	/// @param[in] force Forces the new selection, even if the widget believes the selection to not have changed.
 	/// @param[in] force Forces the new selection, even if the widget believes the selection to not have changed.
-	void SetSelection(int selection, bool force = false);
+	void SetSelection(Element* option_element, bool force = false);
+	/// Seek to the next or previous valid (visible and not disabled) option. Wraps around.
+	/// @param[in] seek_forward True to select the next valid option, false to select the previous valid option.
+	void SeekSelection(bool seek_forward = true);
 	/// Returns the index of the currently selected item.
 	/// Returns the index of the currently selected item.
-	/// @return The index of the currently selected item.
 	int GetSelection() const;
 	int GetSelection() const;
 
 
 	/// Adds a new option to the select control.
 	/// Adds a new option to the select control.
@@ -77,32 +76,32 @@ public:
 	int AddOption(const String& rml, const String& value, int before, bool select, bool selectable = true);
 	int AddOption(const String& rml, const String& value, int before, bool select, bool selectable = true);
 	/// Moves an option element to the select control.
 	/// Moves an option element to the select control.
 	/// @param[in] element Element to move.
 	/// @param[in] element Element to move.
-	/// @param[in] value The value of the option.
 	/// @param[in] before The index of the element to insert the new option before.
 	/// @param[in] before The index of the element to insert the new option before.
-	/// @param[in] select True to select the new option.
-	/// @param[in] selectable If true this option can be selected. If false, this option is not selectable.
 	/// @return The index of the new option, or -1 if invalid.
 	/// @return The index of the new option, or -1 if invalid.
-	int AddOption(ElementPtr element, const String& value, int before, bool select, bool selectable);
+	int AddOption(ElementPtr element, int before);
 	/// Removes an option from the select control.
 	/// Removes an option from the select control.
 	/// @param[in] index The index of the option to remove.
 	/// @param[in] index The index of the option to remove.
 	void RemoveOption(int index);
 	void RemoveOption(int index);
 	/// Removes all options from the list.
 	/// Removes all options from the list.
 	void ClearOptions();
 	void ClearOptions();
 
 
-	/// Returns on of the widget's options.
+	/// Returns one of the widget's options.
 	/// @param[in] The index of the desired option.
 	/// @param[in] The index of the desired option.
-	/// @return The option. This may be nullptr if the index was out of bounds.
-	SelectOption* GetOption(int index);
+	/// @return The option element or nullptr if the index was out of bounds.
+	Element* GetOption(int index);
 	/// Returns the number of options in the widget.
 	/// Returns the number of options in the widget.
 	/// @return The number of options.
 	/// @return The number of options.
 	int GetNumOptions() const;
 	int GetNumOptions() const;
 
 
+	// Handle newly added option elements.
+	void OnChildAdd(Element* element);
+	// Handle newly removed option elements.
+	void OnChildRemove(Element* element);
+
 	/// Processes the incoming event.
 	/// Processes the incoming event.
 	void ProcessEvent(Event& event) override;
 	void ProcessEvent(Event& event) override;
 
 
 private:
 private:
-	typedef Vector< SelectOption > OptionList;
-
 	// Shows or hides the selection box.
 	// Shows or hides the selection box.
 	void ShowSelectBox(bool show);
 	void ShowSelectBox(bool show);
 
 
@@ -117,15 +116,10 @@ private:
 	Element* selection_element;
 	Element* selection_element;
 	Element* value_element;
 	Element* value_element;
 
 
-	// The options in the drop down.
-	OptionList options;
-	int selected_option;
-
-	// The current value of the widget.
-	String value;
-
-	bool box_layout_dirty;
+	bool selection_dirty;
+	bool value_rml_dirty;
 	bool value_layout_dirty;
 	bool value_layout_dirty;
+	bool box_layout_dirty;
 	bool box_visible;
 	bool box_visible;
 };
 };
 
 

+ 87 - 0
Source/Core/Elements/XMLNodeHandlerSelect.cpp

@@ -0,0 +1,87 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * 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 "XMLNodeHandlerSelect.h"
+#include "../../../Include/RmlUi/Core/Log.h"
+#include "../../../Include/RmlUi/Core/Factory.h"
+#include "../../../Include/RmlUi/Core/XMLParser.h"
+#include "../../../Include/RmlUi/Core/Elements/ElementFormControlSelect.h"
+
+namespace Rml {
+
+XMLNodeHandlerSelect::XMLNodeHandlerSelect()
+{}
+
+XMLNodeHandlerSelect::~XMLNodeHandlerSelect()
+{}
+
+Element* XMLNodeHandlerSelect::ElementStart(XMLParser* parser, const String& name, const XMLAttributes& attributes)
+{
+	RMLUI_ASSERT(name == "select" || name == "option");
+
+	if (name == "select")
+	{
+		// Call this node handler for all children
+		parser->PushHandler("select");
+
+		// Attempt to instance the tabset
+		ElementPtr element = Factory::InstanceElement(parser->GetParseFrame()->element, name, name, attributes);
+		ElementFormControlSelect* select_element = rmlui_dynamic_cast<ElementFormControlSelect*>(element.get());
+		if (!select_element)
+		{
+			Log::Message(Log::LT_ERROR, "Instancer failed to create element for tag %s.", name.c_str());
+			return nullptr;
+		}
+
+		// Add the Select element into the document
+		Element* result = parser->GetParseFrame()->element->AppendChild(std::move(element));
+
+		return result;
+	}
+	else if (name == "option")
+	{
+		// Call default element handler for all children.
+		parser->PushDefaultHandler();
+
+		ElementPtr option_element = Factory::InstanceElement(parser->GetParseFrame()->element, name, name, attributes);
+		Element* result = nullptr;
+
+		ElementFormControlSelect* select_element = rmlui_dynamic_cast<ElementFormControlSelect*>(parser->GetParseFrame()->element);
+		if (select_element)
+		{
+			result = option_element.get();
+			select_element->Add(std::move(option_element));
+		}
+
+		return result;
+	}
+
+	return nullptr;
+}
+
+} // namespace Rml

+ 15 - 18
Source/Core/Elements/SelectOption.cpp → Source/Core/Elements/XMLNodeHandlerSelect.h

@@ -26,29 +26,26 @@
  *
  *
  */
  */
 
 
-#include "../../../Include/RmlUi/Core/Elements/SelectOption.h"
+#ifndef RMLUI_CORE_ELEMENTS_XMLNODEHANDLERSELECT_H
+#define RMLUI_CORE_ELEMENTS_XMLNODEHANDLERSELECT_H
 
 
-namespace Rml {
+#include "../XMLNodeHandlerDefault.h"
 
 
-SelectOption::SelectOption(Element* _element, const String& value, bool selectable) : value(value) , selectable(selectable)
-{
-	element = _element;
-}
+namespace Rml {
 
 
-SelectOption::~SelectOption()
-{
-}
+/**
+	XML node handler for processing the select and option tags.
+ */
 
 
-// Returns the element that represents the option visually.
-Element* SelectOption::GetElement()
+class XMLNodeHandlerSelect : public XMLNodeHandlerDefault
 {
 {
-	return element;
-}
+public:
+	XMLNodeHandlerSelect();
+	virtual ~XMLNodeHandlerSelect();
 
 
-// Returns the value of the option.
-const String& SelectOption::GetValue() const
-{
-	return value;
-}
+	/// Called when a new element start is opened
+	Element* ElementStart(XMLParser* parser, const String& name, const XMLAttributes& attributes) override;
+};
 
 
 } // namespace Rml
 } // namespace Rml
+#endif

+ 2 - 0
Source/Core/Factory.cpp

@@ -82,6 +82,7 @@
 #include "Elements/ElementLabel.h"
 #include "Elements/ElementLabel.h"
 #include "Elements/ElementTextSelection.h"
 #include "Elements/ElementTextSelection.h"
 #include "Elements/XMLNodeHandlerDataGrid.h"
 #include "Elements/XMLNodeHandlerDataGrid.h"
+#include "Elements/XMLNodeHandlerSelect.h"
 #include "Elements/XMLNodeHandlerTabSet.h"
 #include "Elements/XMLNodeHandlerTabSet.h"
 #include "Elements/XMLNodeHandlerTextArea.h"
 #include "Elements/XMLNodeHandlerTextArea.h"
 
 
@@ -291,6 +292,7 @@ bool Factory::Initialise()
 	XMLParser::RegisterNodeHandler("datagrid", MakeShared<XMLNodeHandlerDataGrid>());
 	XMLParser::RegisterNodeHandler("datagrid", MakeShared<XMLNodeHandlerDataGrid>());
 	XMLParser::RegisterNodeHandler("tabset", MakeShared<XMLNodeHandlerTabSet>());
 	XMLParser::RegisterNodeHandler("tabset", MakeShared<XMLNodeHandlerTabSet>());
 	XMLParser::RegisterNodeHandler("textarea", MakeShared<XMLNodeHandlerTextArea>());
 	XMLParser::RegisterNodeHandler("textarea", MakeShared<XMLNodeHandlerTextArea>());
+	XMLParser::RegisterNodeHandler("select", MakeShared<XMLNodeHandlerSelect>());
 
 
 	return true;
 	return true;
 }
 }

+ 3 - 3
Source/Lua/Elements/SelectOptionsProxy.cpp

@@ -44,12 +44,12 @@ int SelectOptionsProxy__index(lua_State* L)
         SelectOptionsProxy* proxy = LuaType<SelectOptionsProxy>::check(L,1);
         SelectOptionsProxy* proxy = LuaType<SelectOptionsProxy>::check(L,1);
         RMLUI_CHECK_OBJ(proxy);
         RMLUI_CHECK_OBJ(proxy);
         int index = (int)luaL_checkinteger(L,2);
         int index = (int)luaL_checkinteger(L,2);
-        SelectOption* opt = proxy->owner->GetOption(index-1);
+        Element* opt = proxy->owner->GetOption(index-1);
         RMLUI_CHECK_OBJ(opt);
         RMLUI_CHECK_OBJ(opt);
         lua_newtable(L);
         lua_newtable(L);
-        LuaType<Element>::push(L,opt->GetElement(),false);
+        LuaType<Element>::push(L,opt,false);
         lua_setfield(L,-2,"element");
         lua_setfield(L,-2,"element");
-        lua_pushstring(L,opt->GetValue().c_str());
+        lua_pushstring(L,opt->GetAttribute("value",String()).c_str());
         lua_setfield(L,-2,"value");
         lua_setfield(L,-2,"value");
         return 1;
         return 1;
     }
     }