Browse Source

Add selection API to text input elements: ElementFormControlInput and ElementFormControlTextArea, see #419

Michael Ragazzon 2 years ago
parent
commit
89e42b3efd

+ 9 - 0
Include/RmlUi/Core/Elements/ElementFormControlInput.h

@@ -63,6 +63,15 @@ public:
 	/// @return True if the form control is to be submitted, false otherwise.
 	/// @return True if the form control is to be submitted, false otherwise.
 	bool IsSubmitted() override;
 	bool IsSubmitted() override;
 
 
+	/// Select all text.
+	/// @note Only applies to text and password input types.
+	void Select();
+	/// Select the text in the given character range.
+	/// @param[in] selection_start The first character to be selected.
+	/// @param[in] selection_end The first character *after* the selection.
+	/// @note Only applies to text and password input types.
+	void SetSelectionRange(int selection_start, int selection_end);
+
 protected:
 protected:
 	/// Updates the element's underlying type.
 	/// Updates the element's underlying type.
 	void OnUpdate() override;
 	void OnUpdate() override;

+ 9 - 2
Include/RmlUi/Core/Elements/ElementFormControlTextArea.h

@@ -34,7 +34,7 @@
 
 
 namespace Rml {
 namespace Rml {
 
 
-class WidgetTextInput;
+class WidgetTextInputMultiLine;
 
 
 /**
 /**
 	Default RmlUi implemention of a text area.
 	Default RmlUi implemention of a text area.
@@ -91,6 +91,13 @@ public:
 	/// @return True if the text area is word-wrapping, false otherwise.
 	/// @return True if the text area is word-wrapping, false otherwise.
 	bool GetWordWrap();
 	bool GetWordWrap();
 
 
+	/// Select all text.
+	void Select();
+	/// Select the text in the given character range.
+	/// @param[in] selection_start The first character to be selected.
+	/// @param[in] selection_end The first character *after* the selection.
+	void SetSelectionRange(int selection_start, int selection_end);
+
 	/// Returns the control's inherent size, based on the length of the input field and the current font size.
 	/// Returns the control's inherent size, based on the length of the input field and the current font size.
 	/// @return True.
 	/// @return True.
 	bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) override;
 	bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) override;
@@ -116,7 +123,7 @@ protected:
 	void GetInnerRML(String& content) const override;
 	void GetInnerRML(String& content) const override;
 
 
 private:
 private:
-	WidgetTextInput* widget;		
+	UniquePtr<WidgetTextInputMultiLine> widget;
 };
 };
 
 
 } // namespace Rml
 } // namespace Rml

+ 12 - 0
Source/Core/Elements/ElementFormControlInput.cpp

@@ -68,6 +68,18 @@ bool ElementFormControlInput::IsSubmitted()
 	return type->IsSubmitted();
 	return type->IsSubmitted();
 }
 }
 
 
+void ElementFormControlInput::Select()
+{
+	RMLUI_ASSERT(type);
+	type->Select();
+}
+
+void ElementFormControlInput::SetSelectionRange(int selection_start, int selection_end)
+{
+	RMLUI_ASSERT(type);
+	type->SetSelectionRange(selection_start, selection_end);
+}
+
 // Updates the element's underlying type.
 // Updates the element's underlying type.
 void ElementFormControlInput::OnUpdate()
 void ElementFormControlInput::OnUpdate()
 {
 {

+ 12 - 5
Source/Core/Elements/ElementFormControlTextArea.cpp

@@ -39,17 +39,14 @@ namespace Rml {
 // Constructs a new ElementFormControlTextArea.
 // Constructs a new ElementFormControlTextArea.
 ElementFormControlTextArea::ElementFormControlTextArea(const String& tag) : ElementFormControl(tag)
 ElementFormControlTextArea::ElementFormControlTextArea(const String& tag) : ElementFormControl(tag)
 {
 {
-	widget = new WidgetTextInputMultiLine(this);
+	widget = MakeUnique<WidgetTextInputMultiLine>(this);
 
 
 	SetProperty(PropertyId::OverflowX, Property(Style::Overflow::Auto));
 	SetProperty(PropertyId::OverflowX, Property(Style::Overflow::Auto));
 	SetProperty(PropertyId::OverflowY, Property(Style::Overflow::Auto));
 	SetProperty(PropertyId::OverflowY, Property(Style::Overflow::Auto));
 	SetProperty(PropertyId::WhiteSpace, Property(Style::WhiteSpace::Prewrap));
 	SetProperty(PropertyId::WhiteSpace, Property(Style::WhiteSpace::Prewrap));
 }
 }
 
 
-ElementFormControlTextArea::~ElementFormControlTextArea()
-{
-	delete widget;
-}
+ElementFormControlTextArea::~ElementFormControlTextArea() {}
 
 
 // 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 ElementFormControlTextArea::GetValue() const
 String ElementFormControlTextArea::GetValue() const
@@ -119,6 +116,16 @@ bool ElementFormControlTextArea::GetWordWrap()
 	return attribute != "nowrap";
 	return attribute != "nowrap";
 }
 }
 
 
+void ElementFormControlTextArea::Select()
+{
+	widget->Select();
+}
+
+void ElementFormControlTextArea::SetSelectionRange(int selection_start, int selection_end)
+{
+	widget->SetSelectionRange(selection_start, selection_end);
+}
+
 // Returns the control's inherent size, based on the length of the input field and the current font size.
 // Returns the control's inherent size, based on the length of the input field and the current font size.
 bool ElementFormControlTextArea::GetIntrinsicDimensions(Vector2f& dimensions, float& /*ratio*/)
 bool ElementFormControlTextArea::GetIntrinsicDimensions(Vector2f& dimensions, float& /*ratio*/)
 {
 {

+ 4 - 0
Source/Core/Elements/InputType.cpp

@@ -93,4 +93,8 @@ void InputType::OnChildRemove()
 {
 {
 }
 }
 
 
+void InputType::Select() {}
+
+void InputType::SetSelectionRange(int /*selection_start*/, int /*selection_end*/) {}
+
 } // namespace Rml
 } // namespace Rml

+ 5 - 0
Source/Core/Elements/InputType.h

@@ -88,6 +88,11 @@ public:
 	/// Sizes the dimensions to the element's inherent size.
 	/// Sizes the dimensions to the element's inherent size.
 	virtual bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) = 0;
 	virtual bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) = 0;
 
 
+	/// Select all text.
+	virtual void Select();
+	/// Select the text in the given character range.
+	virtual void SetSelectionRange(int selection_start, int selection_end);
+
 protected:
 protected:
 	ElementFormControlInput* element;
 	ElementFormControlInput* element;
 };
 };

+ 10 - 0
Source/Core/Elements/InputTypeText.cpp

@@ -126,4 +126,14 @@ bool InputTypeText::GetIntrinsicDimensions(Vector2f& dimensions, float& /*ratio*
 	return true;
 	return true;
 }
 }
 
 
+void InputTypeText::Select()
+{
+	widget->Select();
+}
+
+void InputTypeText::SetSelectionRange(int selection_start, int selection_end)
+{
+	widget->SetSelectionRange(selection_start, selection_end);
+}
+
 } // namespace Rml
 } // namespace Rml

+ 5 - 0
Source/Core/Elements/InputTypeText.h

@@ -81,6 +81,11 @@ public:
 	/// @return True.
 	/// @return True.
 	bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) override;
 	bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) override;
 
 
+	/// Select all text.
+	void Select() override;
+	/// Select the text in the given character range.
+	void SetSelectionRange(int selection_start, int selection_end) override;
+
 private:
 private:
 	int size = 20;
 	int size = 20;
 
 

+ 52 - 13
Source/Core/Elements/WidgetTextInput.cpp

@@ -60,20 +60,31 @@ static CharacterClass GetCharacterClass(char c)
 	return CharacterClass::Whitespace;
 	return CharacterClass::Whitespace;
 }
 }
 
 
+static int ConvertCharacterOffsetToByteOffset(const String& value, int character_offset)
+{
+	if (character_offset >= (int)value.size())
+		return (int)value.size();
+
+	int character_count = 0;
+	for (auto it = StringIteratorU8(value); it; ++it)
+	{
+		character_count += 1;
+		if (character_count > character_offset)
+			return (int)it.offset();
+	}
+	return (int)value.size();
+}
+
 // Clamps the value to the given maximum number of unicode code points. Returns true if the value was changed.
 // Clamps the value to the given maximum number of unicode code points. Returns true if the value was changed.
 static bool ClampValue(String& value, int max_length)
 static bool ClampValue(String& value, int max_length)
 {
 {
 	if (max_length >= 0)
 	if (max_length >= 0)
 	{
 	{
-		int num_characters = 0;
-		for (auto it = StringIteratorU8(value); it; ++it)
+		int max_byte_length = ConvertCharacterOffsetToByteOffset(value, max_length);
+		if (max_byte_length < (int)value.size())
 		{
 		{
-			num_characters += 1;
-			if (num_characters > max_length)
-			{
-				value.erase(size_t(it.offset()));
-				return true;
-			}
+			value.erase((size_t)max_byte_length);
+			return true;
 		}
 		}
 	}
 	}
 	return false;
 	return false;
@@ -206,6 +217,38 @@ int WidgetTextInput::GetLength() const
 	return (int)result;
 	return (int)result;
 }
 }
 
 
+void WidgetTextInput::Select()
+{
+	SetSelectionRange(0, INT_MAX);
+}
+
+void WidgetTextInput::SetSelectionRange(int selection_start, int selection_end)
+{
+	const String& value = GetValue();
+	const int byte_start = ConvertCharacterOffsetToByteOffset(value, selection_start);
+	const int byte_end = ConvertCharacterOffsetToByteOffset(value, selection_end);
+	const bool is_selecting = (byte_start != byte_end);
+	
+	cursor_wrap_down = true;
+	absolute_cursor_index = byte_end;
+
+	bool selection_changed = false;
+	if (is_selecting)
+	{
+		selection_anchor_index = byte_start;
+		selection_changed = UpdateSelection(true);
+	}
+	else
+	{
+		selection_changed = UpdateSelection(false);
+	}
+
+	UpdateCursorPosition(true);
+
+	if (selection_changed)
+		FormatText();
+}
+
 // Update the colours of the selected text.
 // Update the colours of the selected text.
 void WidgetTextInput::UpdateSelectionColours()
 void WidgetTextInput::UpdateSelectionColours()
 {
 {
@@ -384,10 +427,7 @@ void WidgetTextInput::ProcessEvent(Event& event)
 		case Input::KI_A:
 		case Input::KI_A:
 		{
 		{
 			if (ctrl)
 			if (ctrl)
-			{
-				selection_changed = MoveCursorHorizontal(CursorMovement::Begin, false);
-				selection_changed |= MoveCursorHorizontal(CursorMovement::End, true);
-			}
+				Select();
 		}
 		}
 		break;
 		break;
 
 
@@ -1180,7 +1220,6 @@ void WidgetTextInput::UpdateCursorPosition(bool update_ideal_cursor_position)
 		ideal_cursor_position = cursor_position.x;
 		ideal_cursor_position = cursor_position.x;
 }
 }
 
 
-// Expand the text selection to the position of the cursor.
 bool WidgetTextInput::UpdateSelection(bool selecting)
 bool WidgetTextInput::UpdateSelection(bool selecting)
 {
 {
 	bool selection_changed = false;
 	bool selection_changed = false;

+ 7 - 0
Source/Core/Elements/WidgetTextInput.h

@@ -65,6 +65,13 @@ public:
 	/// Returns the current length (in characters) of this text field.
 	/// Returns the current length (in characters) of this text field.
 	int GetLength() const;
 	int GetLength() const;
 
 
+	/// Select all text.
+	void Select();
+	/// Select the text in the given character range.
+	/// @param[in] selection_start The first character to be selected.
+	/// @param[in] selection_end The first character *after* the selection.
+	void SetSelectionRange(int selection_start, int selection_end);
+
 	/// Update the colours of the selected text.
 	/// Update the colours of the selected text.
 	void UpdateSelectionColours();
 	void UpdateSelectionColours();
 	/// Generates the text cursor.
 	/// Generates the text cursor.