diff options
author | crupest <crupest@outlook.com> | 2020-10-28 17:44:06 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2020-10-28 17:44:06 +0800 |
commit | 864b031211322dc276b220ec0a6e11483503a0e9 (patch) | |
tree | c5c0aa1a046b443dc001445f39870cb440b6fe38 /src | |
parent | 2df47ffbfff02fb6b64d19e404adc41a93677afe (diff) | |
download | cru-864b031211322dc276b220ec0a6e11483503a0e9.tar.gz cru-864b031211322dc276b220ec0a6e11483503a0e9.tar.bz2 cru-864b031211322dc276b220ec0a6e11483503a0e9.zip |
...
Diffstat (limited to 'src')
-rw-r--r-- | src/common/StringUtil.cpp | 13 | ||||
-rw-r--r-- | src/ui/controls/TextControlService.hpp | 122 |
2 files changed, 103 insertions, 32 deletions
diff --git a/src/common/StringUtil.cpp b/src/common/StringUtil.cpp index fc6d6349..3c312d49 100644 --- a/src/common/StringUtil.cpp +++ b/src/common/StringUtil.cpp @@ -1,4 +1,5 @@ #include "cru/common/StringUtil.hpp" +#include "gsl/gsl_util" namespace cru { namespace { @@ -191,8 +192,8 @@ void Utf8EncodeCodePointAppend(CodePoint code_point, std::string& str) { } void Utf16EncodeCodePointAppend(CodePoint code_point, std::u16string& str) { - if (code_point >= 0 && code_point <= 0xD7FF || - code_point >= 0xE000 && code_point <= 0xFFFF) { + if ((code_point >= 0 && code_point <= 0xD7FF) || + (code_point >= 0xE000 && code_point <= 0xFFFF)) { str.push_back(static_cast<char16_t>(code_point)); } else if (code_point >= 0x10000 && code_point <= 0x10FFFF) { std::uint32_t u = code_point - 0x10000; @@ -220,4 +221,12 @@ std::u16string ToUtf16(std::string_view s) { } return result; } + +bool Utf16IsValidInsertPosition(std::u16string_view s, gsl::index position) { + if (position < 0) return false; + if (position > static_cast<gsl::index>(s.size())) return false; + if (position == 0) return true; + if (position == static_cast<gsl::index>(s.size())) return true; + return !IsUtf16SurrogatePairTrailing(s[position]); +} } // namespace cru diff --git a/src/ui/controls/TextControlService.hpp b/src/ui/controls/TextControlService.hpp index c1a879f6..04807c30 100644 --- a/src/ui/controls/TextControlService.hpp +++ b/src/ui/controls/TextControlService.hpp @@ -1,4 +1,5 @@ #pragma once +#include <string> #include "../Helper.hpp" #include "cru/common/Logger.hpp" #include "cru/common/StringUtil.hpp" @@ -7,6 +8,7 @@ #include "cru/platform/native/InputMethod.hpp" #include "cru/platform/native/UiApplication.hpp" #include "cru/platform/native/Window.hpp" +#include "cru/ui/Base.hpp" #include "cru/ui/Control.hpp" #include "cru/ui/ShortcutHub.hpp" #include "cru/ui/UiEvent.hpp" @@ -14,6 +16,7 @@ #include "cru/ui/render/CanvasRenderObject.hpp" #include "cru/ui/render/ScrollRenderObject.hpp" #include "cru/ui/render/TextRenderObject.hpp" +#include "gsl/gsl_util" namespace cru::ui::controls { constexpr int k_default_caret_blink_duration = 500; @@ -64,13 +67,73 @@ class TextControlService : public Object { std::u16string_view GetTextView() { return this->text_; } void SetText(std::u16string text, bool stop_composition = false) { this->text_ = std::move(text); + CoerceSelection(); + if (stop_composition && this->input_method_context_) { + this->input_method_context_->CancelComposition(); + } + SyncTextRenderObject(); + } + + void InsertText(gsl::index position, std::u16string_view text, + bool stop_composition = false) { + if (!Utf16IsValidInsertPosition(this->text_, position)) { + log::TagError(log_tag, u"Invalid text insert position."); + return; + } + this->text_.insert(this->text_.cbegin() + position, text.begin(), + text.end()); if (stop_composition && this->input_method_context_) { this->input_method_context_->CancelComposition(); } - CoerceSelection(); SyncTextRenderObject(); } + void DeleteChar(gsl::index position, bool stop_composition = false) { + if (!Utf16IsValidInsertPosition(this->text_, position)) { + log::TagError(log_tag, u"Invalid text delete position."); + return; + } + if (position == static_cast<gsl::index>(this->text_.size())) return; + Index next; + Utf16NextCodePoint(this->text_, position, &next); + this->DeleteText(TextRange::FromTwoSides(position, next), stop_composition); + } + + // Return the position of deleted character. + gsl::index DeleteCharPrevious(gsl::index position, + bool stop_composition = false) { + if (!Utf16IsValidInsertPosition(this->text_, position)) { + log::TagError(log_tag, u"Invalid text delete position."); + return 0; + } + if (position == 0) return 0; + Index previous; + Utf16PreviousCodePoint(this->text_, position, &previous); + this->DeleteText(TextRange::FromTwoSides(previous, position), + stop_composition); + return previous; + } + + void DeleteText(TextRange range, bool stop_composition = false) { + if (range.count == 0) return; + range = range.Normalize(); + if (!Utf16IsValidInsertPosition(this->text_, range.GetStart())) { + log::TagError(log_tag, u"Invalid text delete start position."); + return; + } + if (!Utf16IsValidInsertPosition(this->text_, range.GetStart())) { + log::TagError(log_tag, u"Invalid text delete end position."); + return; + } + this->text_.erase(this->text_.cbegin() + range.GetStart(), + this->text_.cbegin() + range.GetEnd()); + this->CoerceSelection(); + if (stop_composition && this->input_method_context_) { + this->input_method_context_->CancelComposition(); + } + this->SyncTextRenderObject(); + } + std::optional<platform::native::CompositionText> GetCompositionInfo() { if (this->input_method_context_ == nullptr) return std::nullopt; auto composition_info = this->input_method_context_->GetCompositionText(); @@ -126,21 +189,33 @@ class TextControlService : public Object { CoerceSelection(); SyncTextRenderObject(); if (scroll_to_caret) { - if (const auto scroll_render_object = this->GetScrollRenderObject()) { - const auto caret_rect = this->GetTextRenderObject()->GetCaretRect(); - // TODO: Wait a tick for layout completed. - this->GetScrollRenderObject()->ScrollToContain(caret_rect, - Thickness{5.f}); - } + this->ScrollToCaret(); } } void DeleteSelectedText() { - auto selection = GetSelection().Normalize(); - if (selection.count == 0) return; - this->text_.erase(this->text_.cbegin() + selection.GetStart(), - this->text_.cbegin() + selection.GetEnd()); - SetSelection(selection.GetStart()); + this->DeleteText(GetSelection()); + SetSelection(GetSelection().GetStart()); + } + + // If some text is selected, then they are deleted first. Then insert text + // into caret position. + void ReplaceSelectedText(std::u16string_view text) { + DeleteSelectedText(); + InsertText(GetSelection().GetStart(), text); + SetSelection(GetSelection().GetStart() + text.size()); + } + + void ScrollToCaret(bool next_tick = true) { + if (next_tick) { + scroll_to_caret_timer_canceler_.Reset( + GetUiApplication()->GetInstance()->SetImmediate( + [this]() { this->ScrollToCaret(false); })); + } else { + const auto caret_rect = this->GetTextRenderObject()->GetCaretRect(); + this->GetScrollRenderObject()->ScrollToContain(caret_rect, + Thickness{5.f}); + } } private: @@ -278,14 +353,7 @@ class TextControlService : public Object { if (!IsEditable()) return; const auto selection = GetSelection(); if (selection.count == 0) { - const auto text = this->GetTextView(); - const auto caret_position = GetCaretPosition(); - if (caret_position == 0) return; - gsl::index new_position; - Utf16PreviousCodePoint(text, caret_position, &new_position); - text_.erase(text_.cbegin() + new_position, - text_.cbegin() + caret_position); - SetSelection(new_position); + SetSelection(DeleteCharPrevious(GetCaretPosition())); } else { this->DeleteSelectedText(); } @@ -294,14 +362,7 @@ class TextControlService : public Object { if (!IsEditable()) return; const auto selection = GetSelection(); if (selection.count == 0) { - const auto text = this->GetTextView(); - const auto caret_position = GetCaretPosition(); - if (caret_position == static_cast<gsl::index>(text.size())) return; - gsl::index new_position; - Utf16NextCodePoint(text, caret_position, &new_position); - text_.erase(text_.cbegin() + caret_position, - text_.cbegin() + new_position); - SyncTextRenderObject(); + DeleteChar(GetCaretPosition()); } else { this->DeleteSelectedText(); } @@ -362,8 +423,7 @@ class TextControlService : public Object { input_method_context_->TextEvent()->AddHandler( [this](const std::u16string_view& text) { if (text == u"\b") return; - this->text_.insert(GetCaretPosition(), text); - this->SetSelection(GetCaretPosition() + text.size()); + this->ReplaceSelectedText(text); }); } } @@ -393,6 +453,8 @@ class TextControlService : public Object { ShortcutHub shortcut_hub_; + platform::native::TimerAutoCanceler scroll_to_caret_timer_canceler_; + // nullopt means not selecting std::optional<MouseButton> select_down_button_; |