From da7ad0ff5c5b158be69c6cf9a2c8e9fc9ef2b3cb Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 25 Dec 2020 15:38:18 +0800 Subject: ... --- src/ui/controls/TextHostControlService.cpp | 458 +++++++++++++++++++++++++++++ 1 file changed, 458 insertions(+) create mode 100644 src/ui/controls/TextHostControlService.cpp (limited to 'src/ui/controls/TextHostControlService.cpp') diff --git a/src/ui/controls/TextHostControlService.cpp b/src/ui/controls/TextHostControlService.cpp new file mode 100644 index 00000000..1ce3f642 --- /dev/null +++ b/src/ui/controls/TextHostControlService.cpp @@ -0,0 +1,458 @@ +#include "cru/ui/controls/TextHostControlService.hpp" + +#include "../Helper.hpp" +#include "cru/common/Logger.hpp" +#include "cru/common/StringUtil.hpp" +#include "cru/platform/graphics/Font.hpp" +#include "cru/platform/graphics/Painter.hpp" +#include "cru/platform/gui/Cursor.hpp" +#include "cru/platform/gui/InputMethod.hpp" +#include "cru/platform/gui/UiApplication.hpp" +#include "cru/platform/gui/Window.hpp" +#include "cru/ui/Base.hpp" +#include "cru/ui/DebugFlags.hpp" +#include "cru/ui/controls/Control.hpp" +#include "cru/ui/events/UiEvent.hpp" +#include "cru/ui/helper/ShortcutHub.hpp" +#include "cru/ui/host/WindowHost.hpp" +#include "cru/ui/render/CanvasRenderObject.hpp" +#include "cru/ui/render/ScrollRenderObject.hpp" +#include "cru/ui/render/TextRenderObject.hpp" +#include "gsl/gsl_assert" +#include "gsl/pointers" + +namespace cru::ui::controls { +TextHostControlService::TextHostControlService(gsl::not_null control) + : control_(control), + text_host_control_(dynamic_cast(control.get())) {} + +void TextHostControlService::SetEnabled(bool enable) { + if (enable == this->enable_) return; + this->enable_ = enable; + if (enable) { + this->SetUpHandlers(); + if (this->caret_visible_) { + this->SetupCaret(); + } + this->control_->SetCursor( + GetUiApplication()->GetCursorManager()->GetSystemCursor( + platform::gui::SystemCursorType::IBeam)); + } else { + this->AbortSelection(); + this->TearDownHandlers(); + this->TearDownCaret(); + this->control_->SetCursor(nullptr); + } +} + +void TextHostControlService::SetEditable(bool editable) { + this->editable_ = editable; + if (!editable) CancelComposition(); +} + +void TextHostControlService::SetText(std::u16string text, + bool stop_composition) { + this->text_ = std::move(text); + CoerceSelection(); + if (stop_composition) { + CancelComposition(); + } + SyncTextRenderObject(); +} + +void TextHostControlService::InsertText(gsl::index position, + std::u16string_view text, + bool stop_composition) { + 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) { + CancelComposition(); + } + SyncTextRenderObject(); +} + +void TextHostControlService::DeleteChar(gsl::index position, + bool stop_composition) { + if (!Utf16IsValidInsertPosition(this->text_, position)) { + log::TagError(log_tag, u"Invalid text delete position."); + return; + } + if (position == static_cast(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 TextHostControlService::DeleteCharPrevious(gsl::index position, + bool stop_composition) { + 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 TextHostControlService::DeleteText(TextRange range, + bool stop_composition) { + 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) { + CancelComposition(); + } + this->SyncTextRenderObject(); +} + +platform::gui::IInputMethodContext* +TextHostControlService ::GetInputMethodContext() { + host::WindowHost* host = this->control_->GetWindowHost(); + if (!host) return nullptr; + platform::gui::INativeWindow* native_window = host->GetNativeWindow(); + if (!native_window) return nullptr; + return native_window->GetInputMethodContext(); +} + +void TextHostControlService::CancelComposition() { + auto input_method_context = GetInputMethodContext(); + if (input_method_context == nullptr) return; + input_method_context->CancelComposition(); +} + +std::optional +TextHostControlService::GetCompositionInfo() { + auto input_method_context = GetInputMethodContext(); + if (input_method_context == nullptr) return std::nullopt; + auto composition_info = input_method_context->GetCompositionText(); + if (composition_info.text.empty()) return std::nullopt; + return composition_info; +} + +void TextHostControlService::SetCaretVisible(bool visible) { + if (visible == this->caret_visible_) return; + + this->caret_visible_ = visible; + + if (this->enable_) { + if (visible) { + this->SetupCaret(); + } else { + this->TearDownCaret(); + } + } +} + +void TextHostControlService::SetCaretBlinkDuration(int milliseconds) { + if (this->caret_blink_duration_ == milliseconds) return; + + if (this->enable_ && this->caret_visible_) { + this->TearDownCaret(); + this->SetupCaret(); + } +} + +void TextHostControlService::ScrollToCaret() { + if (const auto scroll_render_object = this->GetScrollRenderObject()) { + this->control_->GetWindowHost()->RunAfterLayoutStable( + [this, scroll_render_object]() { + const auto caret_rect = this->GetTextRenderObject()->GetCaretRect(); + scroll_render_object->ScrollToContain(caret_rect, Thickness{5.f}); + }); + } +} + +gsl::not_null +TextHostControlService::GetTextRenderObject() { + return this->text_host_control_->GetTextRenderObject(); +} + +render::ScrollRenderObject* TextHostControlService::GetScrollRenderObject() { + return this->text_host_control_->GetScrollRenderObject(); +} + +void TextHostControlService::SetSelection(gsl::index caret_position) { + this->SetSelection(TextRange{caret_position, 0}); +} + +void TextHostControlService::SetSelection(TextRange selection, + bool scroll_to_caret) { + this->selection_ = selection; + CoerceSelection(); + SyncTextRenderObject(); + if (scroll_to_caret) { + this->ScrollToCaret(); + } +} + +void TextHostControlService::ReplaceSelectedText(std::u16string_view text) { + DeleteSelectedText(); + InsertText(GetSelection().GetStart(), text); + SetSelection(GetSelection().GetStart() + text.size()); +} + +void TextHostControlService::DeleteSelectedText() { + this->DeleteText(GetSelection()); + SetSelection(GetSelection().Normalize().GetStart()); +} + +void TextHostControlService::SetupCaret() { + const auto application = GetUiApplication(); + this->GetTextRenderObject()->SetDrawCaret(true); + this->caret_timer_canceler_.Reset(application->SetInterval( + std::chrono::milliseconds(this->caret_blink_duration_), + [this] { this->GetTextRenderObject()->ToggleDrawCaret(); })); +} + +void TextHostControlService::TearDownCaret() { + this->caret_timer_canceler_.Reset(); + this->GetTextRenderObject()->SetDrawCaret(false); +} + +void TextHostControlService::CoerceSelection() { + this->selection_ = this->selection_.CoerceInto(0, text_.size()); +} + +void TextHostControlService::StartSelection(Index start) { + SetSelection(start); + if constexpr (debug_flags::text_service) + log::TagDebug(log_tag, u"Text selection started, position: {}.", start); +} + +void TextHostControlService::UpdateSelection(Index new_end) { + auto selection = GetSelection(); + selection.ChangeEnd(new_end); + this->SetSelection(selection); + if constexpr (debug_flags::text_service) + log::TagDebug(log_tag, u"Text selection updated, range: {}, {}.", + selection.GetStart(), selection.GetEnd()); +} + +void TextHostControlService::SyncTextRenderObject() { + const auto text_render_object = this->GetTextRenderObject(); + const auto composition_info = this->GetCompositionInfo(); + if (composition_info) { + const auto caret_position = GetCaretPosition(); + auto text = this->text_; + text.insert(caret_position, composition_info->text); + text_render_object->SetText(text); + text_render_object->SetCaretPosition(caret_position + + composition_info->selection.GetEnd()); + auto selection = composition_info->selection; + selection.position += caret_position; + text_render_object->SetSelectionRange(selection); + } else { + text_render_object->SetText(this->text_); + text_render_object->SetCaretPosition(this->GetCaretPosition()); + text_render_object->SetSelectionRange(this->GetSelection()); + } +} + +void TextHostControlService::AbortSelection() { + if (this->mouse_move_selecting_) { + this->control_->ReleaseMouse(); + this->mouse_move_selecting_ = false; + } + this->GetTextRenderObject()->SetSelectionRange(std::nullopt); +} + +void TextHostControlService::UpdateInputMethodPosition() { + if (auto input_method_context = this->GetInputMethodContext()) { + Point right_bottom = + this->GetTextRenderObject()->GetTotalOffset() + + this->GetTextRenderObject()->GetCaretRect().GetRightBottom(); + right_bottom.x += 5; + right_bottom.y += 5; + + if constexpr (debug_flags::text_service) { + log::TagDebug(log_tag, + u"Calculate input method candidate window position: {}.", + right_bottom.ToDebugString()); + } + + input_method_context->SetCandidateWindowPosition(right_bottom); + } +} + +void TextHostControlService::TearDownHandlers() { + event_guard_.Clear(); + shortcut_hub_.Uninstall(); +} +void TextHostControlService::SetUpHandlers() { + Expects(event_guard_.IsEmpty()); + + SetupOneHandler(&Control::MouseMoveEvent, + &TextHostControlService::MouseMoveHandler); + SetupOneHandler(&Control::MouseDownEvent, + &TextHostControlService::MouseDownHandler); + SetupOneHandler(&Control::MouseUpEvent, + &TextHostControlService::MouseUpHandler); + SetupOneHandler(&Control::KeyDownEvent, + &TextHostControlService::KeyDownHandler); + SetupOneHandler(&Control::GainFocusEvent, + &TextHostControlService::GainFocusHandler); + SetupOneHandler(&Control::LoseFocusEvent, + &TextHostControlService::LoseFocusHandler); + + shortcut_hub_.Install(control_); +} + +void TextHostControlService::MouseDownHandler( + event::MouseButtonEventArgs& args) { + if (this->mouse_move_selecting_) { + return; + } else { + this->control_->SetFocus(); + if (!this->control_->CaptureMouse()) return; + this->mouse_move_selecting_ = true; + const auto text_render_object = this->GetTextRenderObject(); + const auto result = text_render_object->TextHitTest( + args.GetPointToContent(text_render_object)); + const auto position = result.position + (result.trailing ? 1 : 0); + StartSelection(position); + } +} + +void TextHostControlService::MouseUpHandler(event::MouseButtonEventArgs&) { + if (mouse_move_selecting_) { + this->control_->ReleaseMouse(); + this->mouse_move_selecting_ = false; + } +} + +void TextHostControlService::MouseMoveHandler(event::MouseEventArgs& args) { + if (this->mouse_move_selecting_) { + const auto text_render_object = this->GetTextRenderObject(); + const auto result = text_render_object->TextHitTest( + args.GetPointToContent(text_render_object)); + const auto position = result.position + (result.trailing ? 1 : 0); + UpdateSelection(position); + } +} + +void TextHostControlService::KeyDownHandler(event::KeyEventArgs& args) { + const auto key_code = args.GetKeyCode(); + using cru::platform::gui::KeyCode; + using cru::platform::gui::KeyModifiers; + + switch (key_code) { + case KeyCode::Backspace: { + if (!IsEditable()) return; + const auto selection = GetSelection(); + if (selection.count == 0) { + SetSelection(DeleteCharPrevious(GetCaretPosition())); + } else { + this->DeleteSelectedText(); + } + } break; + case KeyCode::Delete: { + if (!IsEditable()) return; + const auto selection = GetSelection(); + if (selection.count == 0) { + DeleteChar(GetCaretPosition()); + } else { + this->DeleteSelectedText(); + } + } break; + case KeyCode::Left: { + const auto key_modifier = args.GetKeyModifier(); + const bool shift = key_modifier & KeyModifiers::shift; + auto text = this->GetTextView(); + if (shift) { + auto selection = this->GetSelection(); + gsl::index new_position; + Utf16PreviousCodePoint(text, selection.GetEnd(), &new_position); + selection.ChangeEnd(new_position); + this->SetSelection(selection); + } else { + const auto caret = this->GetCaretPosition(); + gsl::index new_position; + Utf16PreviousCodePoint(text, caret, &new_position); + this->SetSelection(new_position); + } + } break; + case KeyCode::Right: { + const auto key_modifier = args.GetKeyModifier(); + const bool shift = key_modifier & KeyModifiers::shift; + auto text = this->GetTextView(); + if (shift) { + auto selection = this->GetSelection(); + gsl::index new_position; + Utf16NextCodePoint(text, selection.GetEnd(), &new_position); + selection.ChangeEnd(new_position); + this->SetSelection(selection); + } else { + const auto caret = this->GetCaretPosition(); + gsl::index new_position; + Utf16NextCodePoint(text, caret, &new_position); + this->SetSelection(new_position); + } + } break; + default: + break; + } +} + +void TextHostControlService::GainFocusHandler( + event::FocusChangeEventArgs& args) { + CRU_UNUSED(args); + if (editable_) { + auto input_method_context = GetInputMethodContext(); + if (input_method_context == nullptr) return; + input_method_context->EnableIME(); + auto sync = [this](std::nullptr_t) { + this->SyncTextRenderObject(); + ScrollToCaret(); + }; + input_method_context_event_guard_ += + input_method_context->CompositionStartEvent()->AddHandler( + [this](std::nullptr_t) { this->DeleteSelectedText(); }); + input_method_context_event_guard_ += + input_method_context->CompositionEvent()->AddHandler(sync); + input_method_context_event_guard_ += + input_method_context->CompositionEndEvent()->AddHandler(sync); + input_method_context_event_guard_ += + input_method_context->TextEvent()->AddHandler( + [this](const std::u16string_view& text) { + if (text == u"\b") return; + this->ReplaceSelectedText(text); + }); + + host::WindowHost* window_host = control_->GetWindowHost(); + if (window_host) + input_method_context_event_guard_ += + window_host->AfterLayoutEvent()->AddHandler( + [this](auto) { this->UpdateInputMethodPosition(); }); + SetCaretVisible(true); + } +} + +void TextHostControlService::LoseFocusHandler( + event::FocusChangeEventArgs& args) { + if (!args.IsWindow()) this->AbortSelection(); + input_method_context_event_guard_.Clear(); + auto input_method_context = GetInputMethodContext(); + if (input_method_context) { + input_method_context->DisableIME(); + } + SetCaretVisible(false); + SyncTextRenderObject(); +} +} // namespace cru::ui::controls -- cgit v1.2.3 From 61b835d47ab98279c5f2f43bebcd4fe18f7c408d Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 25 Dec 2020 15:43:20 +0800 Subject: ... --- src/ui/controls/TextHostControlService.cpp | 8 -------- 1 file changed, 8 deletions(-) (limited to 'src/ui/controls/TextHostControlService.cpp') diff --git a/src/ui/controls/TextHostControlService.cpp b/src/ui/controls/TextHostControlService.cpp index 1ce3f642..7126f7b7 100644 --- a/src/ui/controls/TextHostControlService.cpp +++ b/src/ui/controls/TextHostControlService.cpp @@ -3,23 +3,15 @@ #include "../Helper.hpp" #include "cru/common/Logger.hpp" #include "cru/common/StringUtil.hpp" -#include "cru/platform/graphics/Font.hpp" -#include "cru/platform/graphics/Painter.hpp" #include "cru/platform/gui/Cursor.hpp" #include "cru/platform/gui/InputMethod.hpp" #include "cru/platform/gui/UiApplication.hpp" #include "cru/platform/gui/Window.hpp" -#include "cru/ui/Base.hpp" #include "cru/ui/DebugFlags.hpp" -#include "cru/ui/controls/Control.hpp" -#include "cru/ui/events/UiEvent.hpp" #include "cru/ui/helper/ShortcutHub.hpp" #include "cru/ui/host/WindowHost.hpp" -#include "cru/ui/render/CanvasRenderObject.hpp" #include "cru/ui/render/ScrollRenderObject.hpp" #include "cru/ui/render/TextRenderObject.hpp" -#include "gsl/gsl_assert" -#include "gsl/pointers" namespace cru::ui::controls { TextHostControlService::TextHostControlService(gsl::not_null control) -- cgit v1.2.3 From 715be3c81b96fcf87c7650501d71480a8743a984 Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 26 Dec 2020 19:39:24 +0800 Subject: ... --- include/cru/platform/gui/Keyboard.hpp | 1 + include/cru/ui/controls/TextHostControlService.hpp | 16 +- include/cru/ui/helper/ShortcutHub.hpp | 9 +- src/ui/controls/TextHostControlService.cpp | 242 ++++++++++----------- src/ui/helper/ShortcutHub.cpp | 13 +- 5 files changed, 143 insertions(+), 138 deletions(-) (limited to 'src/ui/controls/TextHostControlService.cpp') diff --git a/include/cru/platform/gui/Keyboard.hpp b/include/cru/platform/gui/Keyboard.hpp index e12cccda..6c29239b 100644 --- a/include/cru/platform/gui/Keyboard.hpp +++ b/include/cru/platform/gui/Keyboard.hpp @@ -116,6 +116,7 @@ struct TagKeyModifier {}; using KeyModifier = Bitmask; struct KeyModifiers { + static constexpr KeyModifier none{0}; static constexpr KeyModifier shift{0b1}; static constexpr KeyModifier ctrl{0b10}; static constexpr KeyModifier alt{0b100}; diff --git a/include/cru/ui/controls/TextHostControlService.hpp b/include/cru/ui/controls/TextHostControlService.hpp index 0bea52c8..9e6a08bc 100644 --- a/include/cru/ui/controls/TextHostControlService.hpp +++ b/include/cru/ui/controls/TextHostControlService.hpp @@ -70,8 +70,10 @@ class TextHostControlService : public Object { void SetSelection(gsl::index caret_position); void SetSelection(TextRange selection, bool scroll_to_caret = true); - void DeleteSelectedText(); + void ChangeSelectionEnd(gsl::index new_end); + void AbortSelection(); + void DeleteSelectedText(); // If some text is selected, then they are deleted first. Then insert text // into caret position. void ReplaceSelectedText(std::u16string_view text); @@ -92,10 +94,6 @@ class TextHostControlService : public Object { void SyncTextRenderObject(); - void StartSelection(Index start); - void UpdateSelection(Index new_end); - void AbortSelection(); - void UpdateInputMethodPosition(); template @@ -106,16 +104,14 @@ class TextHostControlService : public Object { std::bind(handler, this, std::placeholders::_1)); } - void SetUpHandlers(); - void TearDownHandlers(); - void MouseMoveHandler(event::MouseEventArgs& args); void MouseDownHandler(event::MouseButtonEventArgs& args); void MouseUpHandler(event::MouseButtonEventArgs& args); - void KeyDownHandler(event::KeyEventArgs& args); void GainFocusHandler(event::FocusChangeEventArgs& args); void LoseFocusHandler(event::FocusChangeEventArgs& args); + void SetUpShortcuts(); + private: gsl::not_null control_; gsl::not_null text_host_control_; @@ -136,6 +132,6 @@ class TextHostControlService : public Object { helper::ShortcutHub shortcut_hub_; // true if left mouse is down and selecting - bool mouse_move_selecting_; + bool mouse_move_selecting_ = false; }; } // namespace cru::ui::controls diff --git a/include/cru/ui/helper/ShortcutHub.hpp b/include/cru/ui/helper/ShortcutHub.hpp index a4ff2da2..fe3414fe 100644 --- a/include/cru/ui/helper/ShortcutHub.hpp +++ b/include/cru/ui/helper/ShortcutHub.hpp @@ -19,8 +19,9 @@ namespace cru::ui::helper { class ShortcutKeyBind { public: - ShortcutKeyBind(platform::gui::KeyCode key, - platform::gui::KeyModifier modifier) + ShortcutKeyBind( + platform::gui::KeyCode key, + platform::gui::KeyModifier modifier = platform::gui::KeyModifiers::none) : key_(key), modifier_(modifier) {} CRU_DEFAULT_COPY(ShortcutKeyBind) @@ -111,6 +112,8 @@ class ShortcutHub : public Object { const std::vector& GetShortcutByKeyBind( const ShortcutKeyBind& key_bind) const; + IEvent* FallbackKeyEvent() { return &fallback_event_; } + void Install(controls::Control* control); void Uninstall(); @@ -124,6 +127,8 @@ class ShortcutHub : public Object { int current_id_ = 1; + Event fallback_event_; + EventRevokerListGuard event_guard_; }; } // namespace cru::ui::helper diff --git a/src/ui/controls/TextHostControlService.cpp b/src/ui/controls/TextHostControlService.cpp index 7126f7b7..91ec53ff 100644 --- a/src/ui/controls/TextHostControlService.cpp +++ b/src/ui/controls/TextHostControlService.cpp @@ -3,11 +3,15 @@ #include "../Helper.hpp" #include "cru/common/Logger.hpp" #include "cru/common/StringUtil.hpp" +#include "cru/platform/gui/Base.hpp" #include "cru/platform/gui/Cursor.hpp" #include "cru/platform/gui/InputMethod.hpp" +#include "cru/platform/gui/Keyboard.hpp" #include "cru/platform/gui/UiApplication.hpp" #include "cru/platform/gui/Window.hpp" +#include "cru/ui/Base.hpp" #include "cru/ui/DebugFlags.hpp" +#include "cru/ui/events/UiEvent.hpp" #include "cru/ui/helper/ShortcutHub.hpp" #include "cru/ui/host/WindowHost.hpp" #include "cru/ui/render/ScrollRenderObject.hpp" @@ -16,13 +20,27 @@ namespace cru::ui::controls { TextHostControlService::TextHostControlService(gsl::not_null control) : control_(control), - text_host_control_(dynamic_cast(control.get())) {} + text_host_control_(dynamic_cast(control.get())) { + SetUpShortcuts(); + + SetupOneHandler(&Control::MouseMoveEvent, + &TextHostControlService::MouseMoveHandler); + SetupOneHandler(&Control::MouseDownEvent, + &TextHostControlService::MouseDownHandler); + SetupOneHandler(&Control::MouseUpEvent, + &TextHostControlService::MouseUpHandler); + SetupOneHandler(&Control::GainFocusEvent, + &TextHostControlService::GainFocusHandler); + SetupOneHandler(&Control::LoseFocusEvent, + &TextHostControlService::LoseFocusHandler); + + shortcut_hub_.Install(control_); +} void TextHostControlService::SetEnabled(bool enable) { if (enable == this->enable_) return; this->enable_ = enable; if (enable) { - this->SetUpHandlers(); if (this->caret_visible_) { this->SetupCaret(); } @@ -31,7 +49,6 @@ void TextHostControlService::SetEnabled(bool enable) { platform::gui::SystemCursorType::IBeam)); } else { this->AbortSelection(); - this->TearDownHandlers(); this->TearDownCaret(); this->control_->SetCursor(nullptr); } @@ -194,6 +211,20 @@ void TextHostControlService::SetSelection(TextRange selection, } } +void TextHostControlService::ChangeSelectionEnd(Index new_end) { + auto selection = GetSelection(); + selection.ChangeEnd(new_end); + this->SetSelection(selection); +} + +void TextHostControlService::AbortSelection() { + if (this->mouse_move_selecting_) { + this->control_->ReleaseMouse(); + this->mouse_move_selecting_ = false; + } + SetSelection(GetCaretPosition()); +} + void TextHostControlService::ReplaceSelectedText(std::u16string_view text) { DeleteSelectedText(); InsertText(GetSelection().GetStart(), text); @@ -222,21 +253,6 @@ void TextHostControlService::CoerceSelection() { this->selection_ = this->selection_.CoerceInto(0, text_.size()); } -void TextHostControlService::StartSelection(Index start) { - SetSelection(start); - if constexpr (debug_flags::text_service) - log::TagDebug(log_tag, u"Text selection started, position: {}.", start); -} - -void TextHostControlService::UpdateSelection(Index new_end) { - auto selection = GetSelection(); - selection.ChangeEnd(new_end); - this->SetSelection(selection); - if constexpr (debug_flags::text_service) - log::TagDebug(log_tag, u"Text selection updated, range: {}, {}.", - selection.GetStart(), selection.GetEnd()); -} - void TextHostControlService::SyncTextRenderObject() { const auto text_render_object = this->GetTextRenderObject(); const auto composition_info = this->GetCompositionInfo(); @@ -257,14 +273,6 @@ void TextHostControlService::SyncTextRenderObject() { } } -void TextHostControlService::AbortSelection() { - if (this->mouse_move_selecting_) { - this->control_->ReleaseMouse(); - this->mouse_move_selecting_ = false; - } - this->GetTextRenderObject()->SetSelectionRange(std::nullopt); -} - void TextHostControlService::UpdateInputMethodPosition() { if (auto input_method_context = this->GetInputMethodContext()) { Point right_bottom = @@ -283,47 +291,25 @@ void TextHostControlService::UpdateInputMethodPosition() { } } -void TextHostControlService::TearDownHandlers() { - event_guard_.Clear(); - shortcut_hub_.Uninstall(); -} -void TextHostControlService::SetUpHandlers() { - Expects(event_guard_.IsEmpty()); - - SetupOneHandler(&Control::MouseMoveEvent, - &TextHostControlService::MouseMoveHandler); - SetupOneHandler(&Control::MouseDownEvent, - &TextHostControlService::MouseDownHandler); - SetupOneHandler(&Control::MouseUpEvent, - &TextHostControlService::MouseUpHandler); - SetupOneHandler(&Control::KeyDownEvent, - &TextHostControlService::KeyDownHandler); - SetupOneHandler(&Control::GainFocusEvent, - &TextHostControlService::GainFocusHandler); - SetupOneHandler(&Control::LoseFocusEvent, - &TextHostControlService::LoseFocusHandler); - - shortcut_hub_.Install(control_); -} - void TextHostControlService::MouseDownHandler( event::MouseButtonEventArgs& args) { - if (this->mouse_move_selecting_) { - return; - } else { + if (IsEnabled()) { this->control_->SetFocus(); - if (!this->control_->CaptureMouse()) return; - this->mouse_move_selecting_ = true; - const auto text_render_object = this->GetTextRenderObject(); - const auto result = text_render_object->TextHitTest( - args.GetPointToContent(text_render_object)); - const auto position = result.position + (result.trailing ? 1 : 0); - StartSelection(position); + if (args.GetButton() == mouse_buttons::left && + !this->mouse_move_selecting_) { + if (!this->control_->CaptureMouse()) return; + this->mouse_move_selecting_ = true; + const auto text_render_object = this->GetTextRenderObject(); + const auto result = text_render_object->TextHitTest( + args.GetPointToContent(text_render_object)); + const auto position = result.position + (result.trailing ? 1 : 0); + SetSelection(position); + } } } -void TextHostControlService::MouseUpHandler(event::MouseButtonEventArgs&) { - if (mouse_move_selecting_) { +void TextHostControlService::MouseUpHandler(event::MouseButtonEventArgs& args) { + if (args.GetButton() == mouse_buttons::left && mouse_move_selecting_) { this->control_->ReleaseMouse(); this->mouse_move_selecting_ = false; } @@ -335,70 +321,7 @@ void TextHostControlService::MouseMoveHandler(event::MouseEventArgs& args) { const auto result = text_render_object->TextHitTest( args.GetPointToContent(text_render_object)); const auto position = result.position + (result.trailing ? 1 : 0); - UpdateSelection(position); - } -} - -void TextHostControlService::KeyDownHandler(event::KeyEventArgs& args) { - const auto key_code = args.GetKeyCode(); - using cru::platform::gui::KeyCode; - using cru::platform::gui::KeyModifiers; - - switch (key_code) { - case KeyCode::Backspace: { - if (!IsEditable()) return; - const auto selection = GetSelection(); - if (selection.count == 0) { - SetSelection(DeleteCharPrevious(GetCaretPosition())); - } else { - this->DeleteSelectedText(); - } - } break; - case KeyCode::Delete: { - if (!IsEditable()) return; - const auto selection = GetSelection(); - if (selection.count == 0) { - DeleteChar(GetCaretPosition()); - } else { - this->DeleteSelectedText(); - } - } break; - case KeyCode::Left: { - const auto key_modifier = args.GetKeyModifier(); - const bool shift = key_modifier & KeyModifiers::shift; - auto text = this->GetTextView(); - if (shift) { - auto selection = this->GetSelection(); - gsl::index new_position; - Utf16PreviousCodePoint(text, selection.GetEnd(), &new_position); - selection.ChangeEnd(new_position); - this->SetSelection(selection); - } else { - const auto caret = this->GetCaretPosition(); - gsl::index new_position; - Utf16PreviousCodePoint(text, caret, &new_position); - this->SetSelection(new_position); - } - } break; - case KeyCode::Right: { - const auto key_modifier = args.GetKeyModifier(); - const bool shift = key_modifier & KeyModifiers::shift; - auto text = this->GetTextView(); - if (shift) { - auto selection = this->GetSelection(); - gsl::index new_position; - Utf16NextCodePoint(text, selection.GetEnd(), &new_position); - selection.ChangeEnd(new_position); - this->SetSelection(selection); - } else { - const auto caret = this->GetCaretPosition(); - gsl::index new_position; - Utf16NextCodePoint(text, caret, &new_position); - this->SetSelection(new_position); - } - } break; - default: - break; + ChangeSelectionEnd(position); } } @@ -447,4 +370,73 @@ void TextHostControlService::LoseFocusHandler( SetCaretVisible(false); SyncTextRenderObject(); } + +void TextHostControlService::SetUpShortcuts() { + using platform::gui::KeyCode; + using platform::gui::KeyModifiers; + + shortcut_hub_.RegisterShortcut(u"Backspace", KeyCode::Backspace, [this] { + if (!IsEnabled()) return false; + if (!IsEditable()) return false; + const auto selection = GetSelection(); + if (selection.count == 0) { + SetSelection(DeleteCharPrevious(GetCaretPosition())); + } else { + this->DeleteSelectedText(); + } + return true; + }); + + shortcut_hub_.RegisterShortcut(u"Delete", KeyCode::Delete, [this] { + if (!IsEnabled()) return false; + if (!IsEditable()) return false; + const auto selection = GetSelection(); + if (selection.count == 0) { + DeleteChar(GetCaretPosition()); + } else { + this->DeleteSelectedText(); + } + return true; + }); + + shortcut_hub_.RegisterShortcut(u"Left", KeyCode::Left, [this] { + auto text = this->GetTextView(); + const auto caret = this->GetCaretPosition(); + gsl::index new_position; + Utf16PreviousCodePoint(text, caret, &new_position); + this->SetSelection(new_position); + return true; + }); + + shortcut_hub_.RegisterShortcut( + u"ShiftLeft", {KeyCode::Left, KeyModifiers::shift}, [this] { + auto text = this->GetTextView(); + auto selection = this->GetSelection(); + gsl::index new_position; + Utf16PreviousCodePoint(text, selection.GetEnd(), &new_position); + selection.ChangeEnd(new_position); + this->SetSelection(selection); + return true; + }); + + shortcut_hub_.RegisterShortcut(u"Right", KeyCode::Right, [this] { + auto text = this->GetTextView(); + const auto caret = this->GetCaretPosition(); + gsl::index new_position; + Utf16NextCodePoint(text, caret, &new_position); + this->SetSelection(new_position); + return true; + }); + + shortcut_hub_.RegisterShortcut( + u"ShiftRight", {KeyCode::Right, KeyModifiers::shift}, [this] { + auto text = this->GetTextView(); + auto selection = this->GetSelection(); + gsl::index new_position; + Utf16NextCodePoint(text, selection.GetEnd(), &new_position); + selection.ChangeEnd(new_position); + this->SetSelection(selection); + return true; + }); +} } // namespace cru::ui::controls diff --git a/src/ui/helper/ShortcutHub.cpp b/src/ui/helper/ShortcutHub.cpp index 823072f2..f35ad0ef 100644 --- a/src/ui/helper/ShortcutHub.cpp +++ b/src/ui/helper/ShortcutHub.cpp @@ -85,6 +85,8 @@ void ShortcutHub::OnKeyDown(event::KeyEventArgs& event) { ShortcutKeyBind key_bind(event.GetKeyCode(), event.GetKeyModifier()); const auto& shortcut_list = this->GetShortcutByKeyBind(key_bind); + bool handled = false; + if constexpr (debug_flags::shortcut) { if (shortcut_list.empty()) { log::Debug(u"No shortcut for key bind {}.", key_bind.ToString()); @@ -100,12 +102,13 @@ void ShortcutHub::OnKeyDown(event::KeyEventArgs& event) { log::Debug(u"Handle {} handled it.", shortcut.name); } + handled = true; event.SetHandled(); break; } else { if constexpr (debug_flags::shortcut) { - log::Debug(u"Handle {} disdn't handle it.", shortcut.name); + log::Debug(u"Handle {} didn't handle it.", shortcut.name); } } } @@ -116,5 +119,13 @@ void ShortcutHub::OnKeyDown(event::KeyEventArgs& event) { key_bind.ToString()); } } + + if (!handled) { + if constexpr (debug_flags::shortcut) { + log::Debug(u"Raise fallback event for unhandled shortcut of key bind {}.", + key_bind.ToString()); + } + fallback_event_.Raise(event); + } } } // namespace cru::ui::helper -- cgit v1.2.3 From 6523ba8e0f2830f3cd4434d8331882077be3f82c Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 26 Dec 2020 23:41:02 +0800 Subject: ... --- include/cru/common/StringUtil.hpp | 19 ++++++++++- src/common/StringUtil.cpp | 55 ++++++++++++++++++++++++++++++ src/ui/controls/TextHostControlService.cpp | 16 +++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) (limited to 'src/ui/controls/TextHostControlService.cpp') diff --git a/include/cru/common/StringUtil.hpp b/include/cru/common/StringUtil.hpp index 276048f5..62999d53 100644 --- a/include/cru/common/StringUtil.hpp +++ b/include/cru/common/StringUtil.hpp @@ -1,7 +1,10 @@ #pragma once -#include #include "Base.hpp" +#include +#include +#include + namespace cru { using CodePoint = std::int32_t; constexpr CodePoint k_invalid_code_point = -1; @@ -128,4 +131,18 @@ std::u16string ToUtf16(std::string_view s); // If given s is not a valid utf16 string, return value is UD. bool Utf16IsValidInsertPosition(std::u16string_view s, gsl::index position); + +// Return position after the character making predicate returns true or 0 if no +// character doing so. +gsl::index Utf16BackwardUntil(std::u16string_view str, gsl::index position, + const std::function& predicate); +// Return position before the character making predicate returns true or +// str.size() if no character doing so. +gsl::index Utf16ForwardUntil(std::u16string_view str, gsl::index position, + const std::function& predicate); + +gsl::index Utf16PreviousWord(std::u16string_view str, gsl::index position, + bool* is_space = nullptr); +gsl::index Utf16NextWord(std::u16string_view str, gsl::index position, + bool* is_space = nullptr); } // namespace cru diff --git a/src/common/StringUtil.cpp b/src/common/StringUtil.cpp index 3c312d49..b13c0193 100644 --- a/src/common/StringUtil.cpp +++ b/src/common/StringUtil.cpp @@ -1,4 +1,5 @@ #include "cru/common/StringUtil.hpp" +#include "cru/common/Base.hpp" #include "gsl/gsl_util" namespace cru { @@ -229,4 +230,58 @@ bool Utf16IsValidInsertPosition(std::u16string_view s, gsl::index position) { if (position == static_cast(s.size())) return true; return !IsUtf16SurrogatePairTrailing(s[position]); } + +gsl::index Utf16BackwardUntil(std::u16string_view str, gsl::index position, + const std::function& predicate) { + if (position <= 0) return position; + while (true) { + gsl::index p = position; + auto c = Utf16PreviousCodePoint(str, p, &position); + if (predicate(c)) return p; + if (c == k_invalid_code_point) return p; + } + UnreachableCode(); +} + +gsl::index Utf16ForwardUntil(std::u16string_view str, gsl::index position, + const std::function& predicate) { + if (position >= static_cast(str.size())) return position; + while (true) { + gsl::index p = position; + auto c = Utf16NextCodePoint(str, p, &position); + if (predicate(c)) return p; + if (c == k_invalid_code_point) return p; + } + UnreachableCode(); +} + +inline bool IsSpace(CodePoint c) { return c == 0x20; } + +gsl::index Utf16PreviousWord(std::u16string_view str, gsl::index position, + bool* is_space) { + if (position <= 0) return position; + auto c = Utf16PreviousCodePoint(str, position, nullptr); + if (IsSpace(c)) { // TODO: Currently only test against 0x20(space). + if (is_space) *is_space = true; + return Utf16BackwardUntil(str, position, + [](CodePoint c) { return !IsSpace(c); }); + } else { + if (is_space) *is_space = false; + return Utf16BackwardUntil(str, position, IsSpace); + } +} + +gsl::index Utf16NextWord(std::u16string_view str, gsl::index position, + bool* is_space) { + if (position >= static_cast(str.size())) return position; + auto c = Utf16NextCodePoint(str, position, nullptr); + if (IsSpace(c)) { // TODO: Currently only test against 0x20(space). + if (is_space) *is_space = true; + return Utf16ForwardUntil(str, position, + [](CodePoint c) { return !IsSpace(c); }); + } else { + if (is_space) *is_space = false; + return Utf16ForwardUntil(str, position, IsSpace); + } +} } // namespace cru diff --git a/src/ui/controls/TextHostControlService.cpp b/src/ui/controls/TextHostControlService.cpp index 91ec53ff..4bfd6715 100644 --- a/src/ui/controls/TextHostControlService.cpp +++ b/src/ui/controls/TextHostControlService.cpp @@ -419,6 +419,14 @@ void TextHostControlService::SetUpShortcuts() { return true; }); + shortcut_hub_.RegisterShortcut( + u"CtrlLeft", {KeyCode::Left, KeyModifiers::ctrl}, [this] { + auto text = this->GetTextView(); + auto caret = this->GetCaretPosition(); + this->SetSelection(Utf16PreviousWord(text, caret)); + return true; + }); + shortcut_hub_.RegisterShortcut(u"Right", KeyCode::Right, [this] { auto text = this->GetTextView(); const auto caret = this->GetCaretPosition(); @@ -438,5 +446,13 @@ void TextHostControlService::SetUpShortcuts() { this->SetSelection(selection); return true; }); + + shortcut_hub_.RegisterShortcut( + u"CtrlRight", {KeyCode::Right, KeyModifiers::ctrl}, [this] { + auto text = this->GetTextView(); + auto caret = this->GetCaretPosition(); + this->SetSelection(Utf16NextWord(text, caret)); + return true; + }); } } // namespace cru::ui::controls -- cgit v1.2.3 From fb280cc30e1012e18b47e43f783ddd4b33bf9d1c Mon Sep 17 00:00:00 2001 From: crupest Date: Mon, 28 Dec 2020 21:23:04 +0800 Subject: ... --- src/ui/controls/TextHostControlService.cpp | 60 ++++++++++++++++++------------ 1 file changed, 36 insertions(+), 24 deletions(-) (limited to 'src/ui/controls/TextHostControlService.cpp') diff --git a/src/ui/controls/TextHostControlService.cpp b/src/ui/controls/TextHostControlService.cpp index 4bfd6715..502f63f9 100644 --- a/src/ui/controls/TextHostControlService.cpp +++ b/src/ui/controls/TextHostControlService.cpp @@ -401,57 +401,69 @@ void TextHostControlService::SetUpShortcuts() { shortcut_hub_.RegisterShortcut(u"Left", KeyCode::Left, [this] { auto text = this->GetTextView(); - const auto caret = this->GetCaretPosition(); - gsl::index new_position; - Utf16PreviousCodePoint(text, caret, &new_position); - this->SetSelection(new_position); + auto caret = this->GetCaretPosition(); + Utf16PreviousCodePoint(text, caret, &caret); + this->SetSelection(caret); return true; }); + shortcut_hub_.RegisterShortcut(u"ShiftLeft", + {KeyCode::Left, KeyModifiers::shift}, [this] { + auto text = this->GetTextView(); + auto caret = this->GetCaretPosition(); + Utf16PreviousCodePoint(text, caret, &caret); + this->ChangeSelectionEnd(caret); + return true; + }); + shortcut_hub_.RegisterShortcut( - u"ShiftLeft", {KeyCode::Left, KeyModifiers::shift}, [this] { + u"CtrlLeft", {KeyCode::Left, KeyModifiers::ctrl}, [this] { auto text = this->GetTextView(); - auto selection = this->GetSelection(); - gsl::index new_position; - Utf16PreviousCodePoint(text, selection.GetEnd(), &new_position); - selection.ChangeEnd(new_position); - this->SetSelection(selection); + auto caret = this->GetCaretPosition(); + this->SetSelection(Utf16PreviousWord(text, caret)); return true; }); shortcut_hub_.RegisterShortcut( - u"CtrlLeft", {KeyCode::Left, KeyModifiers::ctrl}, [this] { + u"CtrlShiftLeft", + {KeyCode::Left, KeyModifiers::ctrl | KeyModifiers::shift}, [this] { auto text = this->GetTextView(); auto caret = this->GetCaretPosition(); - this->SetSelection(Utf16PreviousWord(text, caret)); + this->ChangeSelectionEnd(Utf16PreviousWord(text, caret)); return true; }); shortcut_hub_.RegisterShortcut(u"Right", KeyCode::Right, [this] { auto text = this->GetTextView(); - const auto caret = this->GetCaretPosition(); - gsl::index new_position; - Utf16NextCodePoint(text, caret, &new_position); - this->SetSelection(new_position); + auto caret = this->GetCaretPosition(); + Utf16NextCodePoint(text, caret, &caret); + this->SetSelection(caret); return true; }); + shortcut_hub_.RegisterShortcut(u"ShiftRight", + {KeyCode::Right, KeyModifiers::shift}, [this] { + auto text = this->GetTextView(); + auto caret = this->GetCaretPosition(); + Utf16NextCodePoint(text, caret, &caret); + this->ChangeSelectionEnd(caret); + return true; + }); + shortcut_hub_.RegisterShortcut( - u"ShiftRight", {KeyCode::Right, KeyModifiers::shift}, [this] { + u"CtrlRight", {KeyCode::Right, KeyModifiers::ctrl}, [this] { auto text = this->GetTextView(); - auto selection = this->GetSelection(); - gsl::index new_position; - Utf16NextCodePoint(text, selection.GetEnd(), &new_position); - selection.ChangeEnd(new_position); - this->SetSelection(selection); + auto caret = this->GetCaretPosition(); + this->SetSelection(Utf16NextWord(text, caret)); return true; }); shortcut_hub_.RegisterShortcut( - u"CtrlRight", {KeyCode::Right, KeyModifiers::ctrl}, [this] { + u"CtrlShiftRight", + {KeyCode::Right, KeyModifiers::ctrl | KeyModifiers::shift}, [this] { auto text = this->GetTextView(); auto caret = this->GetCaretPosition(); - this->SetSelection(Utf16NextWord(text, caret)); + this->ChangeSelectionEnd(Utf16NextWord(text, caret)); return true; }); } -- cgit v1.2.3 From 7b6ca6e8a158868da316351b64e64ab1cdb5872c Mon Sep 17 00:00:00 2001 From: crupest Date: Mon, 28 Dec 2020 23:05:06 +0800 Subject: ... --- src/common/StringUtil.cpp | 2 +- src/ui/controls/TextHostControlService.cpp | 1 - src/win/gui/InputMethod.cpp | 10 +++++++--- 3 files changed, 8 insertions(+), 5 deletions(-) (limited to 'src/ui/controls/TextHostControlService.cpp') diff --git a/src/common/StringUtil.cpp b/src/common/StringUtil.cpp index b13c0193..0cadc545 100644 --- a/src/common/StringUtil.cpp +++ b/src/common/StringUtil.cpp @@ -255,7 +255,7 @@ gsl::index Utf16ForwardUntil(std::u16string_view str, gsl::index position, UnreachableCode(); } -inline bool IsSpace(CodePoint c) { return c == 0x20; } +inline bool IsSpace(CodePoint c) { return c == 0x20 || c == 0xA; } gsl::index Utf16PreviousWord(std::u16string_view str, gsl::index position, bool* is_space) { diff --git a/src/ui/controls/TextHostControlService.cpp b/src/ui/controls/TextHostControlService.cpp index 502f63f9..07b4f1e8 100644 --- a/src/ui/controls/TextHostControlService.cpp +++ b/src/ui/controls/TextHostControlService.cpp @@ -346,7 +346,6 @@ void TextHostControlService::GainFocusHandler( input_method_context_event_guard_ += input_method_context->TextEvent()->AddHandler( [this](const std::u16string_view& text) { - if (text == u"\b") return; this->ReplaceSelectedText(text); }); diff --git a/src/win/gui/InputMethod.cpp b/src/win/gui/InputMethod.cpp index 6643ea58..cc237e88 100644 --- a/src/win/gui/InputMethod.cpp +++ b/src/win/gui/InputMethod.cpp @@ -226,7 +226,7 @@ void WinInputMethodContext::OnWindowNativeMessage( const auto& message = args.GetWindowMessage(); switch (message.msg) { case WM_CHAR: { - const auto c = static_cast(message.w_param); + auto c = static_cast(message.w_param); if (IsUtf16SurrogatePairCodeUnit(c)) { // I don't think this will happen because normal key strike without ime // should only trigger ascci character. If it is a charater from @@ -235,8 +235,12 @@ void WinInputMethodContext::OnWindowNativeMessage( u"A WM_CHAR message for character from supplementary " u"planes is ignored."); } else { - char16_t s[1] = {c}; - text_event_.Raise({s, 1}); + if (c != '\b') { // ignore backspace + if (c == '\r') c = '\n'; // Change \r to \n + + char16_t s[1] = {c}; + text_event_.Raise({s, 1}); + } } args.HandleWithResult(0); break; -- cgit v1.2.3