Browse Source

Add GetSelection to textarea and input elements, see #419

Michael Ragazzon 2 years ago
parent
commit
8f120d9612

+ 8 - 2
Include/RmlUi/Core/Elements/ElementFormControlInput.h

@@ -63,14 +63,20 @@ 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.
+	/// Selects all text.
 	/// @note Only applies to text and password input types.
 	/// @note Only applies to text and password input types.
 	void Select();
 	void Select();
-	/// Select the text in the given character range.
+	/// Selects the text in the given character range.
 	/// @param[in] selection_start The first character to be selected.
 	/// @param[in] selection_start The first character to be selected.
 	/// @param[in] selection_end The first character *after* the selection.
 	/// @param[in] selection_end The first character *after* the selection.
 	/// @note Only applies to text and password input types.
 	/// @note Only applies to text and password input types.
 	void SetSelectionRange(int selection_start, int selection_end);
 	void SetSelectionRange(int selection_start, int selection_end);
+	/// Retrieves the selection range and text.
+	/// @param[out] selection_start The first character selected.
+	/// @param[out] selection_end The first character *after* the selection.
+	/// @param[out] selected_text The selected text.
+	/// @note Only applies to text and password input types.
+	void GetSelection(int* selection_start, int* selection_end, String* selected_text) const;
 
 
 protected:
 protected:
 	/// Updates the element's underlying type.
 	/// Updates the element's underlying type.

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

@@ -91,12 +91,17 @@ 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.
+	/// Selects all text.
 	void Select();
 	void Select();
-	/// Select the text in the given character range.
+	/// Selects the text in the given character range.
 	/// @param[in] selection_start The first character to be selected.
 	/// @param[in] selection_start The first character to be selected.
 	/// @param[in] selection_end The first character *after* the selection.
 	/// @param[in] selection_end The first character *after* the selection.
 	void SetSelectionRange(int selection_start, int selection_end);
 	void SetSelectionRange(int selection_start, int selection_end);
+	/// Retrieves the selection range and text.
+	/// @param[out] selection_start The first character selected.
+	/// @param[out] selection_end The first character *after* the selection.
+	/// @param[out] selected_text The selected text.
+	void GetSelection(int* selection_start, int* selection_end, String* selected_text) const;
 
 
 	/// 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.

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

@@ -80,6 +80,12 @@ void ElementFormControlInput::SetSelectionRange(int selection_start, int selecti
 	type->SetSelectionRange(selection_start, selection_end);
 	type->SetSelectionRange(selection_start, selection_end);
 }
 }
 
 
+void ElementFormControlInput::GetSelection(int* selection_start, int* selection_end, String* selected_text) const
+{
+	RMLUI_ASSERT(type);
+	type->GetSelection(selection_start, selection_end, selected_text);
+}
+
 // Updates the element's underlying type.
 // Updates the element's underlying type.
 void ElementFormControlInput::OnUpdate()
 void ElementFormControlInput::OnUpdate()
 {
 {

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

@@ -126,6 +126,11 @@ void ElementFormControlTextArea::SetSelectionRange(int selection_start, int sele
 	widget->SetSelectionRange(selection_start, selection_end);
 	widget->SetSelectionRange(selection_start, selection_end);
 }
 }
 
 
+void ElementFormControlTextArea::GetSelection(int* selection_start, int* selection_end, String* selected_text) const
+{
+	widget->GetSelection(selection_start, selection_end, selected_text);
+}
+
 // 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*/)
 {
 {

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

@@ -97,4 +97,6 @@ void InputType::Select() {}
 
 
 void InputType::SetSelectionRange(int /*selection_start*/, int /*selection_end*/) {}
 void InputType::SetSelectionRange(int /*selection_start*/, int /*selection_end*/) {}
 
 
+void InputType::GetSelection(int* /*selection_start*/, int* /*selection_end*/, String* /*selected_text*/) const {}
+
 } // namespace Rml
 } // namespace Rml

+ 4 - 2
Source/Core/Elements/InputType.h

@@ -88,10 +88,12 @@ 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.
+	/// Selects all text.
 	virtual void Select();
 	virtual void Select();
-	/// Select the text in the given character range.
+	/// Selects the text in the given character range.
 	virtual void SetSelectionRange(int selection_start, int selection_end);
 	virtual void SetSelectionRange(int selection_start, int selection_end);
+	/// Retrieves the selection range and text.
+	virtual void GetSelection(int* selection_start, int* selection_end, String* selected_text) const;
 
 
 protected:
 protected:
 	ElementFormControlInput* element;
 	ElementFormControlInput* element;

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

@@ -136,4 +136,9 @@ void InputTypeText::SetSelectionRange(int selection_start, int selection_end)
 	widget->SetSelectionRange(selection_start, selection_end);
 	widget->SetSelectionRange(selection_start, selection_end);
 }
 }
 
 
+void InputTypeText::GetSelection(int* selection_start, int* selection_end, String* selected_text) const
+{
+	widget->GetSelection(selection_start, selection_end, selected_text);
+}
+
 } // namespace Rml
 } // namespace Rml

+ 4 - 2
Source/Core/Elements/InputTypeText.h

@@ -81,10 +81,12 @@ public:
 	/// @return True.
 	/// @return True.
 	bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) override;
 	bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) override;
 
 
-	/// Select all text.
+	/// Selects all text.
 	void Select() override;
 	void Select() override;
-	/// Select the text in the given character range.
+	/// Selects the text in the given character range.
 	void SetSelectionRange(int selection_start, int selection_end) override;
 	void SetSelectionRange(int selection_start, int selection_end) override;
+	/// Retrieves the selection range and text.
+	void GetSelection(int* selection_start, int* selection_end, String* selected_text) const override;
 
 
 private:
 private:
 	int size = 20;
 	int size = 20;

+ 24 - 1
Source/Core/Elements/WidgetTextInput.cpp

@@ -75,6 +75,18 @@ static int ConvertCharacterOffsetToByteOffset(const String& value, int character
 	return (int)value.size();
 	return (int)value.size();
 }
 }
 
 
+static int ConvertByteOffsetToCharacterOffset(const String& value, int byte_offset)
+{
+	int character_count = 0;
+	for (auto it = StringIteratorU8(value); it; ++it)
+	{
+		if (it.offset() >= byte_offset)
+			break;
+		character_count += 1;
+	}
+	return character_count;
+}
+
 // 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)
 {
 {
@@ -249,6 +261,17 @@ void WidgetTextInput::SetSelectionRange(int selection_start, int selection_end)
 		FormatText();
 		FormatText();
 }
 }
 
 
+void WidgetTextInput::GetSelection(int* selection_start, int* selection_end, String* selected_text) const
+{
+	const String& value = GetValue();
+	if (selection_start)
+		*selection_start = ConvertByteOffsetToCharacterOffset(value, selection_begin_index);
+	if (selection_end)
+		*selection_end = ConvertByteOffsetToCharacterOffset(value, selection_begin_index + selection_length);
+	if (selected_text)
+		*selected_text = value.substr(Math::Min((size_t)selection_begin_index, (size_t)value.size()), (size_t)selection_length);
+}
+
 // Update the colours of the selected text.
 // Update the colours of the selected text.
 void WidgetTextInput::UpdateSelectionColours()
 void WidgetTextInput::UpdateSelectionColours()
 {
 {
@@ -608,7 +631,7 @@ bool WidgetTextInput::DeleteCharacters(CursorMovement direction)
 void WidgetTextInput::CopySelection()
 void WidgetTextInput::CopySelection()
 {
 {
 	const String& value = GetValue();
 	const String& value = GetValue();
-	const String snippet = value.substr(std::min((size_t)selection_begin_index, (size_t)value.size()), (size_t)selection_length);
+	const String snippet = value.substr(Math::Min((size_t)selection_begin_index, (size_t)value.size()), (size_t)selection_length);
 	GetSystemInterface()->SetClipboardText(snippet);
 	GetSystemInterface()->SetClipboardText(snippet);
 }
 }
 
 

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

@@ -65,12 +65,17 @@ 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.
+	/// Selects all text.
 	void Select();
 	void Select();
-	/// Select the text in the given character range.
+	/// Selects the text in the given character range.
 	/// @param[in] selection_start The first character to be selected.
 	/// @param[in] selection_start The first character to be selected.
 	/// @param[in] selection_end The first character *after* the selection.
 	/// @param[in] selection_end The first character *after* the selection.
 	void SetSelectionRange(int selection_start, int selection_end);
 	void SetSelectionRange(int selection_start, int selection_end);
+	/// Retrieves the selection range and text.
+	/// @param[out] selection_start The first character selected.
+	/// @param[out] selection_end The first character *after* the selection.
+	/// @param[out] selected_text The selected text.
+	void GetSelection(int* selection_start, int* selection_end, String* selected_text) const;
 
 
 	/// Update the colours of the selected text.
 	/// Update the colours of the selected text.
 	void UpdateSelectionColours();
 	void UpdateSelectionColours();

+ 46 - 0
Tests/Source/UnitTests/StringUtilities.cpp

@@ -65,3 +65,49 @@ TEST_CASE("StringUtilities::TrimTrailingDotZeros")
 }
 }
 
 
 
 
+
+#include "../../../Source/Core/Elements/WidgetTextInput.cpp"
+
+TEST_CASE("ConvertByteOffsetToCharacterOffset")
+{
+	// clang-format off
+	CHECK(ConvertByteOffsetToCharacterOffset("", 0) == 0);
+	CHECK(ConvertByteOffsetToCharacterOffset("", 1) == 0);
+    CHECK(ConvertByteOffsetToCharacterOffset("a", 0) == 0);
+    CHECK(ConvertByteOffsetToCharacterOffset("a", 1) == 1);
+	CHECK(ConvertByteOffsetToCharacterOffset("ab", 1) == 1);
+	CHECK(ConvertByteOffsetToCharacterOffset("ab", 2) == 2);
+
+	CHECK(ConvertByteOffsetToCharacterOffset("a\xC2\xA3" "b", 1) == 1);
+	CHECK(ConvertByteOffsetToCharacterOffset("a\xC2\xA3" "b", 2) == 2);
+	CHECK(ConvertByteOffsetToCharacterOffset("a\xC2\xA3" "b", 3) == 2);
+	CHECK(ConvertByteOffsetToCharacterOffset("a\xC2\xA3" "b", 4) == 3);
+
+	CHECK(ConvertByteOffsetToCharacterOffset("a\xE2\x82\xAC" "b", 2) == 2);
+	CHECK(ConvertByteOffsetToCharacterOffset("a\xE2\x82\xAC" "b", 3) == 2);
+	CHECK(ConvertByteOffsetToCharacterOffset("a\xE2\x82\xAC" "b", 4) == 2);
+	CHECK(ConvertByteOffsetToCharacterOffset("a\xE2\x82\xAC" "b", 5) == 3);
+	// clang-format on
+}
+
+TEST_CASE("ConvertCharacterOffsetToByteOffset")
+{
+	// clang-format off
+	CHECK(ConvertCharacterOffsetToByteOffset("", 0) == 0);
+	CHECK(ConvertCharacterOffsetToByteOffset("", 1) == 0);
+    CHECK(ConvertCharacterOffsetToByteOffset("a", 0) == 0);
+    CHECK(ConvertCharacterOffsetToByteOffset("a", 1) == 1);
+	CHECK(ConvertCharacterOffsetToByteOffset("ab", 1) == 1);
+	CHECK(ConvertCharacterOffsetToByteOffset("ab", 2) == 2);
+
+	CHECK(ConvertCharacterOffsetToByteOffset("a\xC2\xA3" "b", 1) == 1);
+	CHECK(ConvertCharacterOffsetToByteOffset("a\xC2\xA3" "b", 2) == 3);
+	CHECK(ConvertCharacterOffsetToByteOffset("a\xC2\xA3" "b", 3) == 4);
+	CHECK(ConvertCharacterOffsetToByteOffset("a\xC2\xA3" "b", 4) == 4);
+
+	CHECK(ConvertCharacterOffsetToByteOffset("a\xE2\x82\xAC" "b", 1) == 1);
+	CHECK(ConvertCharacterOffsetToByteOffset("a\xE2\x82\xAC" "b", 2) == 4);
+	CHECK(ConvertCharacterOffsetToByteOffset("a\xE2\x82\xAC" "b", 3) == 5);
+	CHECK(ConvertCharacterOffsetToByteOffset("a\xE2\x82\xAC" "b", 4) == 5);
+	// clang-format on
+}