Browse Source

Various improvements to text boxes.
- Don't decode escape characters in text boxes
- Enable ctrl-backspace/delete deletion.
- Enable ctrl-home/end navigation.
- Enable ctrl-a for select all.
- Improved word navigation.
- Speed up paste and text input insertion, only send a single change event for the whole string.
- In the samples we display a text cursor over text boxes (only on the Windows shell for now)

Michael Ragazzon 6 years ago
parent
commit
aa459ca607

+ 2 - 1
Include/RmlUi/Core/ElementText.h

@@ -69,8 +69,9 @@ public:
 	/// @param[in] maximum_line_width The width (in pixels) of space allowed for the line, or -1 for unlimited space.
 	/// @param[in] maximum_line_width The width (in pixels) of space allowed for the line, or -1 for unlimited space.
 	/// @param[in] right_spacing_width The width (in pixels) of the spacing (consisting of margins, padding, etc) that must be remaining on the right of the line if the last of the text is rendered onto this line.
 	/// @param[in] right_spacing_width The width (in pixels) of the spacing (consisting of margins, padding, etc) that must be remaining on the right of the line if the last of the text is rendered onto this line.
 	/// @param[in] trim_whitespace_prefix If we're collapsing whitespace, whether or not to remove all prefixing whitespace or collapse it down to a single space.
 	/// @param[in] trim_whitespace_prefix If we're collapsing whitespace, whether or not to remove all prefixing whitespace or collapse it down to a single space.
+	/// @param[in] decode_escape_characters Decode escaped characters such as & into &.
 	/// @return True if the line reached the end of the element's text, false if not.
 	/// @return True if the line reached the end of the element's text, false if not.
-	virtual bool GenerateLine(String& line, int& line_length, float& line_width, int line_begin, float maximum_line_width, float right_spacing_width, bool trim_whitespace_prefix) = 0;
+	virtual bool GenerateLine(String& line, int& line_length, float& line_width, int line_begin, float maximum_line_width, float right_spacing_width, bool trim_whitespace_prefix, bool decode_escape_characters) = 0;
 
 
 	/// Clears all lines of generated text and prepares the element for generating new lines.
 	/// Clears all lines of generated text and prepares the element for generating new lines.
 	virtual void ClearLines() = 0;
 	virtual void ClearLines() = 0;

+ 2 - 0
Samples/assets/invader.rcss

@@ -278,12 +278,14 @@ input.text
 	height: 31px;
 	height: 31px;
 	padding: 10px 10px 0px;
 	padding: 10px 10px 0px;
 	decorator: tiled-horizontal( text-l, text-c, auto ); /* Right becomes mirrored left */
 	decorator: tiled-horizontal( text-l, text-c, auto ); /* Right becomes mirrored left */
+	cursor: text;
 }
 }
 
 
 textarea
 textarea
 {
 {
 	padding: 14px 12px 10px;
 	padding: 14px 12px 10px;
 	decorator: ninepatch( textarea, textarea-inner );
 	decorator: ninepatch( textarea, textarea-inner );
+	cursor: text;
 }
 }
 
 
 input.text,
 input.text,

+ 5 - 0
Samples/basic/transform/src/main.cpp

@@ -203,6 +203,11 @@ int main(int RMLUI_UNUSED_PARAMETER(argc), char** RMLUI_UNUSED_PARAMETER(argv))
 
 
 	Shell::EventLoop(GameLoop);
 	Shell::EventLoop(GameLoop);
 
 
+	if (window_1)
+	{
+		context->GetRootElement()->RemoveEventListener(Rml::Core::EventId::Keydown, window_1);
+	}
+
 	delete window_1;
 	delete window_1;
 	delete window_2;
 	delete window_2;
 
 

+ 1 - 1
Samples/shell/src/win32/InputWin32.cpp

@@ -136,7 +136,7 @@ void InputWin32::ProcessWindowsEvent(UINT message, WPARAM w_param, LPARAM l_para
 				first_u16_code_unit = 0;
 				first_u16_code_unit = 0;
 
 
 				// Only send through printable characters.
 				// Only send through printable characters.
-				if ((char32_t)character >= 32 || character == (Rml::Core::Character)'\n')
+				if (((char32_t)character >= 32 || character == (Rml::Core::Character)'\n') && character != (Rml::Core::Character)127)
 					context->ProcessTextInput(character);
 					context->ProcessTextInput(character);
 			}
 			}
 		}
 		}

+ 4 - 0
Samples/shell/src/win32/ShellWin32.cpp

@@ -49,6 +49,7 @@ static std::unique_ptr<ShellFileInterface> file_interface;
 static HCURSOR cursor_default = nullptr;
 static HCURSOR cursor_default = nullptr;
 static HCURSOR cursor_move = nullptr;
 static HCURSOR cursor_move = nullptr;
 static HCURSOR cursor_cross = nullptr;
 static HCURSOR cursor_cross = nullptr;
+static HCURSOR cursor_text = nullptr;
 static HCURSOR cursor_unavailable = nullptr;
 static HCURSOR cursor_unavailable = nullptr;
 
 
 
 
@@ -67,6 +68,7 @@ bool Shell::Initialise()
 	cursor_default = LoadCursor(nullptr, IDC_ARROW);
 	cursor_default = LoadCursor(nullptr, IDC_ARROW);
 	cursor_move = LoadCursor(nullptr, IDC_SIZEALL);
 	cursor_move = LoadCursor(nullptr, IDC_SIZEALL);
 	cursor_cross = LoadCursor(nullptr, IDC_CROSS);
 	cursor_cross = LoadCursor(nullptr, IDC_CROSS);
+	cursor_text = LoadCursor(nullptr, IDC_IBEAM);
 	cursor_unavailable = LoadCursor(nullptr, IDC_NO);
 	cursor_unavailable = LoadCursor(nullptr, IDC_NO);
 
 
 	Rml::Core::String root = FindSamplesRoot();
 	Rml::Core::String root = FindSamplesRoot();
@@ -284,6 +286,8 @@ void Shell::SetMouseCursor(const Rml::Core::String& cursor_name)
 			cursor_handle = cursor_move;
 			cursor_handle = cursor_move;
 		else if (cursor_name == "cross")
 		else if (cursor_name == "cross")
 			cursor_handle = cursor_cross;
 			cursor_handle = cursor_cross;
+		else if (cursor_name == "text")
+			cursor_handle = cursor_text;
 		else if (cursor_name == "unavailable")
 		else if (cursor_name == "unavailable")
 			cursor_handle = cursor_unavailable;
 			cursor_handle = cursor_unavailable;
 
 

+ 47 - 32
Source/Controls/WidgetTextInput.cpp

@@ -314,14 +314,15 @@ void WidgetTextInput::ProcessEvent(Core::Event& event)
 		case Core::Input::KI_DOWN:		MoveCursorVertical(1, shift); break;
 		case Core::Input::KI_DOWN:		MoveCursorVertical(1, shift); break;
 
 
 		case Core::Input::KI_NUMPAD7:	if (numlock) break;
 		case Core::Input::KI_NUMPAD7:	if (numlock) break;
-		case Core::Input::KI_HOME:		MoveCursorHorizontal(CursorMovement::BeginLine, shift); break;
+		case Core::Input::KI_HOME:		MoveCursorHorizontal(ctrl ? CursorMovement::Begin : CursorMovement::BeginLine, shift); break;
 
 
 		case Core::Input::KI_NUMPAD1:	if (numlock) break;
 		case Core::Input::KI_NUMPAD1:	if (numlock) break;
-		case Core::Input::KI_END:		MoveCursorHorizontal(CursorMovement::EndLine, shift); break;
+		case Core::Input::KI_END:		MoveCursorHorizontal(ctrl ? CursorMovement::End : CursorMovement::EndLine, shift); break;
 
 
 		case Core::Input::KI_BACK:
 		case Core::Input::KI_BACK:
 		{
 		{
-			if (DeleteCharacter(true))
+			CursorMovement direction = (ctrl ? CursorMovement::PreviousWord : CursorMovement::Left);
+			if (DeleteCharacters(direction))
 			{
 			{
 				FormatElement();
 				FormatElement();
 				UpdateRelativeCursor();
 				UpdateRelativeCursor();
@@ -334,7 +335,8 @@ void WidgetTextInput::ProcessEvent(Core::Event& event)
 		case Core::Input::KI_DECIMAL:	if (numlock) break;
 		case Core::Input::KI_DECIMAL:	if (numlock) break;
 		case Core::Input::KI_DELETE:
 		case Core::Input::KI_DELETE:
 		{
 		{
-			if (DeleteCharacter(false))
+			CursorMovement direction = (ctrl ? CursorMovement::NextWord : CursorMovement::Right);
+			if (DeleteCharacters(direction))
 			{
 			{
 				FormatElement();
 				FormatElement();
 				UpdateRelativeCursor();
 				UpdateRelativeCursor();
@@ -351,6 +353,16 @@ void WidgetTextInput::ProcessEvent(Core::Event& event)
 		}
 		}
 		break;
 		break;
 
 
+		case Core::Input::KI_A:
+		{
+			if (ctrl)
+			{
+				MoveCursorHorizontal(CursorMovement::Begin, false);
+				MoveCursorHorizontal(CursorMovement::End, true);
+			}
+		}
+		break;
+
 		case Core::Input::KI_C:
 		case Core::Input::KI_C:
 		{
 		{
 			if (ctrl)
 			if (ctrl)
@@ -375,12 +387,7 @@ void WidgetTextInput::ProcessEvent(Core::Event& event)
 				Core::String clipboard_text;
 				Core::String clipboard_text;
 				Core::GetSystemInterface()->GetClipboardText(clipboard_text);
 				Core::GetSystemInterface()->GetClipboardText(clipboard_text);
 
 
-				// @performance: Can be made heaps faster.
-				for (auto it = Core::StringIteratorU8(clipboard_text); it; ++it)
-				{
-					Core::Character character = *it;
-					AddCharacter(character);
-				}
+				AddCharacters(clipboard_text);
 			}
 			}
 		}
 		}
 		break;
 		break;
@@ -405,13 +412,7 @@ void WidgetTextInput::ProcessEvent(Core::Event& event)
 			event.GetParameter< int >("meta_key", 0) == 0)
 			event.GetParameter< int >("meta_key", 0) == 0)
 		{
 		{
 			Core::String text = event.GetParameter("text", Core::String{});
 			Core::String text = event.GetParameter("text", Core::String{});
-
-			// @performance: Can be made heaps faster.
-			for (auto it = Core::StringIteratorU8(text); it; ++it)
-			{
-				Core::Character character = *it;
-				AddCharacter(character);
-			}
+			AddCharacters(text);
 		}
 		}
 
 
 		ShowCursor(true);
 		ShowCursor(true);
@@ -467,9 +468,18 @@ void WidgetTextInput::ProcessEvent(Core::Event& event)
 }
 }
 
 
 // Adds a new character to the string at the cursor position.
 // Adds a new character to the string at the cursor position.
-bool WidgetTextInput::AddCharacter(Rml::Core::Character character)
+bool WidgetTextInput::AddCharacters(Rml::Core::String string)
 {
 {
-	if ((char32_t)character <= 127 && !IsCharacterValid(static_cast<char>(character)))
+	// Erase invalid characters from string
+	auto invalid_character = [this](char c) {
+		return ((unsigned char)c <= 127 && !IsCharacterValid(c));
+	};
+	string.erase(
+		std::remove_if(string.begin(), string.end(), invalid_character),
+		string.end()
+	);
+
+	if (string.empty())
 		return false;
 		return false;
 
 
 	if (selection_length > 0)
 	if (selection_length > 0)
@@ -480,10 +490,9 @@ bool WidgetTextInput::AddCharacter(Rml::Core::Character character)
 
 
 	Core::String value = GetElement()->GetAttribute< Rml::Core::String >("value", "");
 	Core::String value = GetElement()->GetAttribute< Rml::Core::String >("value", "");
 	
 	
-	Core::String insert = Core::StringUtilities::ToUTF8(character);
-	value.insert(GetCursorIndex(), insert);
+	value.insert(GetCursorIndex(), string);
 
 
-	edit_index += (int)insert.size();
+	edit_index += (int)string.size();
 
 
 	GetElement()->SetAttribute("value", value);
 	GetElement()->SetAttribute("value", value);
 	DispatchChangeEvent();
 	DispatchChangeEvent();
@@ -494,12 +503,12 @@ bool WidgetTextInput::AddCharacter(Rml::Core::Character character)
 }
 }
 
 
 // Deletes a character from the string.
 // Deletes a character from the string.
-bool WidgetTextInput::DeleteCharacter(bool back)
+bool WidgetTextInput::DeleteCharacters(CursorMovement direction)
 {
 {
-	// We set a selection of the next or previous character, and then delete it.
+	// We set a selection of characters according to direction, and then delete it.
 	// If we already have a selection, we delete that first.
 	// If we already have a selection, we delete that first.
 	if (selection_length <= 0)
 	if (selection_length <= 0)
-		MoveCursorHorizontal(back ? CursorMovement::Left : CursorMovement::Right, true);
+		MoveCursorHorizontal(direction, true);
 
 
 	if (selection_length > 0)
 	if (selection_length > 0)
 	{
 	{
@@ -540,6 +549,9 @@ void WidgetTextInput::MoveCursorHorizontal(CursorMovement movement, bool select)
 
 
 	switch (movement)
 	switch (movement)
 	{
 	{
+	case CursorMovement::Begin:
+		absolute_cursor_index = 0;
+		break;
 	case CursorMovement::BeginLine:
 	case CursorMovement::BeginLine:
 		absolute_cursor_index -= cursor_character_index;
 		absolute_cursor_index -= cursor_character_index;
 		break;
 		break;
@@ -550,17 +562,17 @@ void WidgetTextInput::MoveCursorHorizontal(CursorMovement movement, bool select)
 		}
 		}
 		else
 		else
 		{
 		{
-			bool whitespace_found = false;
+			bool word_character_found = false;
 			const char* p_rend = lines[cursor_line_index].content.data();
 			const char* p_rend = lines[cursor_line_index].content.data();
 			const char* p_rbegin = p_rend + cursor_character_index;
 			const char* p_rbegin = p_rend + cursor_character_index;
 			const char* p = p_rbegin - 1;
 			const char* p = p_rbegin - 1;
 			for (; p > p_rend; --p)
 			for (; p > p_rend; --p)
 			{
 			{
-				bool is_whitespace = is_nonword_character(*p);
-				if(whitespace_found && !is_whitespace)
+				bool is_word_character = !is_nonword_character(*p);
+				if(word_character_found && !is_word_character)
 					break;
 					break;
-				else if(!whitespace_found && is_whitespace)
-					whitespace_found = true;
+				else if(is_word_character)
+					word_character_found = true;
 			}
 			}
 			if (p != p_rend) ++p;
 			if (p != p_rend) ++p;
 			absolute_cursor_index += int(p - p_rbegin);
 			absolute_cursor_index += int(p - p_rbegin);
@@ -589,7 +601,7 @@ void WidgetTextInput::MoveCursorHorizontal(CursorMovement movement, bool select)
 				bool is_whitespace = is_nonword_character(*p);
 				bool is_whitespace = is_nonword_character(*p);
 				if (whitespace_found && !is_whitespace)
 				if (whitespace_found && !is_whitespace)
 					break;
 					break;
-				else if (!whitespace_found && is_whitespace)
+				else if (is_whitespace)
 					whitespace_found = true;
 					whitespace_found = true;
 			}
 			}
 			absolute_cursor_index += int(p - p_begin);
 			absolute_cursor_index += int(p - p_begin);
@@ -598,6 +610,9 @@ void WidgetTextInput::MoveCursorHorizontal(CursorMovement movement, bool select)
 	case CursorMovement::EndLine:
 	case CursorMovement::EndLine:
 		absolute_cursor_index += lines[cursor_line_index].content_length - cursor_character_index;
 		absolute_cursor_index += lines[cursor_line_index].content_length - cursor_character_index;
 		break;
 		break;
+	case CursorMovement::End:
+		absolute_cursor_index = INT_MAX;
+		break;
 	default:
 	default:
 		break;
 		break;
 	}
 	}
@@ -877,7 +892,7 @@ Rml::Core::Vector2f WidgetTextInput::FormatText()
 		float line_width;
 		float line_width;
 
 
 		// Generate the next line.
 		// Generate the next line.
-		last_line = text_element->GenerateLine(line.content, line.content_length, line_width, line_begin, parent->GetClientWidth() - cursor_size.x, 0, false);
+		last_line = text_element->GenerateLine(line.content, line.content_length, line_width, line_begin, parent->GetClientWidth() - cursor_size.x, 0, false, false);
 
 
 		// If this line terminates in a soft-return, then the line may be leaving a space or two behind as an orphan.
 		// If this line terminates in a soft-return, then the line may be leaving a space or two behind as an orphan.
 		// If so, we must append the orphan onto the line even though it will push the line outside of the input
 		// If so, we must append the orphan onto the line even though it will push the line outside of the input

+ 9 - 8
Source/Controls/WidgetTextInput.h

@@ -87,18 +87,20 @@ public:
 	const Rml::Core::Vector2f& GetTextDimensions() const;
 	const Rml::Core::Vector2f& GetTextDimensions() const;
 
 
 protected:
 protected:
+	enum class CursorMovement { Begin = -4, BeginLine = -3, PreviousWord = -2, Left = -1, Right = 1, NextWord = 2, EndLine = 3, End = 4 };
+
 	/// Processes the "keydown" and "textinput" event to write to the input field, and the "focus" and
 	/// Processes the "keydown" and "textinput" event to write to the input field, and the "focus" and
 	/// "blur" to set the state of the cursor.
 	/// "blur" to set the state of the cursor.
 	void ProcessEvent(Core::Event& event) override;
 	void ProcessEvent(Core::Event& event) override;
 
 
-	/// Adds a new character to the string at the cursor position.
-	/// @param[in] character The character to add to the string.
-	/// @return True if the character was successfully added, false otherwise.
-	bool AddCharacter(Rml::Core::Character character);
-	/// Deletes a character from the string.
-	/// @param[in] backward True to delete a character behind the cursor, false for in front of the cursor.
+	/// Adds new characters to the string at the cursor position.
+	/// @param[in] string The characters to add.
+	/// @return True if at least one character was successfully added, false otherwise.
+	bool AddCharacters(Core::String string);
+	/// Deletes characters from the string.
+	/// @param[in] direction Movement of cursor for deletion.
 	/// @return True if a character was deleted, false otherwise.
 	/// @return True if a character was deleted, false otherwise.
-	bool DeleteCharacter(bool back);
+	bool DeleteCharacters(CursorMovement direction);
 	/// Returns true if the given character is permitted in the input field, false if not.
 	/// Returns true if the given character is permitted in the input field, false if not.
 	/// @param[in] character The character to validate.
 	/// @param[in] character The character to validate.
 	/// @return True if the character is allowed, false if not.
 	/// @return True if the character is allowed, false if not.
@@ -116,7 +118,6 @@ protected:
 	void DispatchChangeEvent(bool linebreak = false);
 	void DispatchChangeEvent(bool linebreak = false);
 
 
 private:
 private:
-	enum class CursorMovement { BeginLine = -3, PreviousWord = -2, Left = -1, Right = 1, NextWord = 2, EndLine = 3 };
 	
 	
 	/// Moves the cursor along the current line.
 	/// Moves the cursor along the current line.
 	/// @param[in] movement Cursor movement operation.
 	/// @param[in] movement Cursor movement operation.

+ 6 - 6
Source/Core/ElementTextDefault.cpp

@@ -38,7 +38,7 @@
 namespace Rml {
 namespace Rml {
 namespace Core {
 namespace Core {
 
 
-static bool BuildToken(String& token, const char*& token_begin, const char* string_end, bool first_token, bool collapse_white_space, bool break_at_endline, Style::TextTransform text_transformation);
+static bool BuildToken(String& token, const char*& token_begin, const char* string_end, bool first_token, bool collapse_white_space, bool break_at_endline, Style::TextTransform text_transformation, bool decode_escape_characters);
 static bool LastToken(const char* token_begin, const char* string_end, bool collapse_white_space, bool break_at_endline);
 static bool LastToken(const char* token_begin, const char* string_end, bool collapse_white_space, bool break_at_endline);
 
 
 ElementTextDefault::ElementTextDefault(const String& tag) : ElementText(tag), colour(255, 255, 255), decoration(this)
 ElementTextDefault::ElementTextDefault(const String& tag) : ElementText(tag), colour(255, 255, 255), decoration(this)
@@ -170,14 +170,14 @@ bool ElementTextDefault::GenerateToken(float& token_width, int line_begin)
 	const char* token_begin = text.c_str() + line_begin;
 	const char* token_begin = text.c_str() + line_begin;
 	String token;
 	String token;
 
 
-	BuildToken(token, token_begin, text.c_str() + text.size(), true, collapse_white_space, break_at_endline, computed.text_transform);
+	BuildToken(token, token_begin, text.c_str() + text.size(), true, collapse_white_space, break_at_endline, computed.text_transform, true);
 	token_width = (float) GetFontEngineInterface()->GetStringWidth(font_face_handle, token);
 	token_width = (float) GetFontEngineInterface()->GetStringWidth(font_face_handle, token);
 
 
 	return LastToken(token_begin, text.c_str() + text.size(), collapse_white_space, break_at_endline);
 	return LastToken(token_begin, text.c_str() + text.size(), collapse_white_space, break_at_endline);
 }
 }
 
 
 // Generates a line of text rendered from this element
 // Generates a line of text rendered from this element
-bool ElementTextDefault::GenerateLine(String& line, int& line_length, float& line_width, int line_begin, float maximum_line_width, float right_spacing_width, bool trim_whitespace_prefix)
+bool ElementTextDefault::GenerateLine(String& line, int& line_length, float& line_width, int line_begin, float maximum_line_width, float right_spacing_width, bool trim_whitespace_prefix, bool decode_escape_characters)
 {
 {
 	RMLUI_ZoneScoped;
 	RMLUI_ZoneScoped;
 
 
@@ -224,7 +224,7 @@ bool ElementTextDefault::GenerateLine(String& line, int& line_length, float& lin
 			previous_codepoint = StringUtilities::ToCharacter(StringUtilities::SeekBackwardUTF8(&line.back(), line.data()));
 			previous_codepoint = StringUtilities::ToCharacter(StringUtilities::SeekBackwardUTF8(&line.back(), line.data()));
 
 
 		// Generate the next token and determine its pixel-length.
 		// Generate the next token and determine its pixel-length.
-		bool break_line = BuildToken(token, next_token_begin, string_end, line.empty() && trim_whitespace_prefix, collapse_white_space, break_at_endline, text_transform_property);
+		bool break_line = BuildToken(token, next_token_begin, string_end, line.empty() && trim_whitespace_prefix, collapse_white_space, break_at_endline, text_transform_property, decode_escape_characters);
 		int token_width = GetFontEngineInterface()->GetStringWidth(font_face_handle, token, previous_codepoint);
 		int token_width = GetFontEngineInterface()->GetStringWidth(font_face_handle, token, previous_codepoint);
 
 
 		// If we're breaking to fit a line box, check if the token can fit on the line before we add it.
 		// If we're breaking to fit a line box, check if the token can fit on the line before we add it.
@@ -432,7 +432,7 @@ void ElementTextDefault::GenerateLineDecoration(const FontFaceHandle font_face_h
 	GeometryUtilities::GenerateLine(font_face_handle, &decoration, line.position, line.width, decoration_property, colour);
 	GeometryUtilities::GenerateLine(font_face_handle, &decoration, line.position, line.width, decoration_property, colour);
 }
 }
 
 
-static bool BuildToken(String& token, const char*& token_begin, const char* string_end, bool first_token, bool collapse_white_space, bool break_at_endline, Style::TextTransform text_transformation)
+static bool BuildToken(String& token, const char*& token_begin, const char* string_end, bool first_token, bool collapse_white_space, bool break_at_endline, Style::TextTransform text_transformation, bool decode_escape_characters)
 {
 {
 	RMLUI_ASSERT(token_begin != string_end);
 	RMLUI_ASSERT(token_begin != string_end);
 
 
@@ -454,7 +454,7 @@ static bool BuildToken(String& token, const char*& token_begin, const char* stri
 		const char* escape_begin = token_begin;
 		const char* escape_begin = token_begin;
 
 
 		// Check for an ampersand; if we find one, we've got an HTML escaped character.
 		// Check for an ampersand; if we find one, we've got an HTML escaped character.
-		if (character == '&')
+		if (decode_escape_characters && character == '&')
 		{
 		{
 			// Find the terminating ';'.
 			// Find the terminating ';'.
 			while (token_begin != string_end &&
 			while (token_begin != string_end &&

+ 2 - 1
Source/Core/ElementTextDefault.h

@@ -64,8 +64,9 @@ public:
 	/// @param[in] maximum_line_width The width (in pixels) of space allowed for the line, or -1 for unlimited space.
 	/// @param[in] maximum_line_width The width (in pixels) of space allowed for the line, or -1 for unlimited space.
 	/// @param[in] right_spacing_width The width (in pixels) of the spacing (consisting of margins, padding, etc) that must be remaining on the right of the line if the last of the text is rendered onto this line.
 	/// @param[in] right_spacing_width The width (in pixels) of the spacing (consisting of margins, padding, etc) that must be remaining on the right of the line if the last of the text is rendered onto this line.
 	/// @param[in] trim_whitespace_prefix If we're collapsing whitespace, whether or remove all prefixing whitespace or collapse it down to a single space.
 	/// @param[in] trim_whitespace_prefix If we're collapsing whitespace, whether or remove all prefixing whitespace or collapse it down to a single space.
+	/// @param[in] decode_escape_characters Decode escaped characters such as &amp; into &.
 	/// @return True if the line reached the end of the element's text, false if not.
 	/// @return True if the line reached the end of the element's text, false if not.
-	bool GenerateLine(String& line, int& line_length, float& line_width, int line_begin, float maximum_line_width, float right_spacing_width, bool trim_whitespace_prefix) override;
+	bool GenerateLine(String& line, int& line_length, float& line_width, int line_begin, float maximum_line_width, float right_spacing_width, bool trim_whitespace_prefix, bool decode_escape_characters) override;
 
 
 	/// Clears all lines of generated text and prepares the element for generating new lines.
 	/// Clears all lines of generated text and prepares the element for generating new lines.
 	void ClearLines() override;
 	void ClearLines() override;

+ 1 - 1
Source/Core/LayoutInlineBoxText.cpp

@@ -64,7 +64,7 @@ LayoutInlineBox* LayoutInlineBoxText::FlowContent(bool first_box, float availabl
 
 
 	int line_length;
 	int line_length;
 	float line_width;
 	float line_width;
-	bool overflow = !text_element->GenerateLine(line_contents, line_length, line_width, line_begin, available_width, right_spacing_width, first_box);
+	bool overflow = !text_element->GenerateLine(line_contents, line_length, line_width, line_begin, available_width, right_spacing_width, first_box, true);
 
 
 	Vector2f content_area;
 	Vector2f content_area;
 	content_area.x = line_width;
 	content_area.x = line_width;