|
@@ -60,6 +60,48 @@ 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();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+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.
|
|
|
|
|
+static bool ClampValue(String& value, int max_length)
|
|
|
|
|
+{
|
|
|
|
|
+ if (max_length >= 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ int max_byte_length = ConvertCharacterOffsetToByteOffset(value, max_length);
|
|
|
|
|
+ if (max_byte_length < (int)value.size())
|
|
|
|
|
+ {
|
|
|
|
|
+ value.erase((size_t)max_byte_length);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
WidgetTextInput::WidgetTextInput(ElementFormControl* _parent) : internal_dimensions(0, 0), scroll_offset(0, 0), selection_geometry(_parent), cursor_position(0, 0), cursor_size(0, 0), cursor_geometry(_parent)
|
|
WidgetTextInput::WidgetTextInput(ElementFormControl* _parent) : internal_dimensions(0, 0), scroll_offset(0, 0), selection_geometry(_parent), cursor_position(0, 0), cursor_size(0, 0), cursor_geometry(_parent)
|
|
|
{
|
|
{
|
|
|
keyboard_showed = false;
|
|
keyboard_showed = false;
|
|
@@ -168,29 +210,10 @@ void WidgetTextInput::SetMaxLength(int _max_length)
|
|
|
if (max_length != _max_length)
|
|
if (max_length != _max_length)
|
|
|
{
|
|
{
|
|
|
max_length = _max_length;
|
|
max_length = _max_length;
|
|
|
- if (max_length >= 0)
|
|
|
|
|
- {
|
|
|
|
|
- String value = GetValue();
|
|
|
|
|
-
|
|
|
|
|
- int num_characters = 0;
|
|
|
|
|
- size_t i_erase = value.size();
|
|
|
|
|
-
|
|
|
|
|
- for (auto it = StringIteratorU8(value); it; ++it)
|
|
|
|
|
- {
|
|
|
|
|
- num_characters += 1;
|
|
|
|
|
- if (num_characters > max_length)
|
|
|
|
|
- {
|
|
|
|
|
- i_erase = size_t(it.offset());
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- if(i_erase < value.size())
|
|
|
|
|
- {
|
|
|
|
|
- value.erase(i_erase);
|
|
|
|
|
- GetElement()->SetAttribute("value", value);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ String value = GetValue();
|
|
|
|
|
+ if (ClampValue(value, max_length))
|
|
|
|
|
+ GetElement()->SetAttribute("value", value);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -206,6 +229,49 @@ 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();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+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()
|
|
|
{
|
|
{
|
|
@@ -384,10 +450,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;
|
|
|
|
|
|
|
@@ -524,13 +587,13 @@ bool WidgetTextInput::AddCharacters(String string)
|
|
|
{
|
|
{
|
|
|
SanitizeValue(string);
|
|
SanitizeValue(string);
|
|
|
|
|
|
|
|
- if (string.empty())
|
|
|
|
|
- return false;
|
|
|
|
|
-
|
|
|
|
|
if (selection_length > 0)
|
|
if (selection_length > 0)
|
|
|
DeleteSelection();
|
|
DeleteSelection();
|
|
|
|
|
|
|
|
- if (max_length >= 0 && GetLength() >= max_length)
|
|
|
|
|
|
|
+ if (max_length >= 0)
|
|
|
|
|
+ ClampValue(string, Math::Max(max_length - GetLength(), 0));
|
|
|
|
|
+
|
|
|
|
|
+ if (string.empty())
|
|
|
return false;
|
|
return false;
|
|
|
|
|
|
|
|
String value = GetAttributeValue();
|
|
String value = GetAttributeValue();
|
|
@@ -569,7 +632,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);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1181,7 +1244,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;
|