From c5a4f8c11d0d84d85359e5ff03477da5e9f12926 Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 18 Mar 2020 22:57:08 +0800 Subject: ... --- src/ui/controls/container.cpp | 2 +- src/ui/controls/text_block.cpp | 3 +- src/ui/controls/text_common.cpp | 165 --------------------- src/ui/controls/text_control_service.hpp | 242 +++++++++++++++++++++++++++++++ 4 files changed, 245 insertions(+), 167 deletions(-) delete mode 100644 src/ui/controls/text_common.cpp create mode 100644 src/ui/controls/text_control_service.hpp (limited to 'src/ui/controls') diff --git a/src/ui/controls/container.cpp b/src/ui/controls/container.cpp index 4695ad4c..84582d80 100644 --- a/src/ui/controls/container.cpp +++ b/src/ui/controls/container.cpp @@ -11,7 +11,7 @@ Container::Container() { Container::~Container() = default; -void Container::OnChildChanged(Control* old_child, Control* new_child) { +void Container::OnChildChanged(Control*, Control* new_child) { render_object_->RemoveChild(0); render_object_->AddChild(new_child->GetRenderObject(), 0); } diff --git a/src/ui/controls/text_block.cpp b/src/ui/controls/text_block.cpp index ece599e8..0a243d2e 100644 --- a/src/ui/controls/text_block.cpp +++ b/src/ui/controls/text_block.cpp @@ -4,6 +4,7 @@ #include "cru/ui/render/stack_layout_render_object.hpp" #include "cru/ui/render/text_render_object.hpp" #include "cru/ui/ui_manager.hpp" +#include "text_control_service.hpp" namespace cru::ui::controls { using render::CanvasRenderObject; @@ -29,7 +30,7 @@ TextBlock::TextBlock() caret_brush_ = theme_resources->caret_brush; - service_ = std::make_unique(this, this); + service_ = std::make_unique>(this); service_->SetEnabled(true); } diff --git a/src/ui/controls/text_common.cpp b/src/ui/controls/text_common.cpp deleted file mode 100644 index fdfdcc2c..00000000 --- a/src/ui/controls/text_common.cpp +++ /dev/null @@ -1,165 +0,0 @@ -#include "cru/ui/controls/text_common.hpp" - -#include "cru/common/logger.hpp" -#include "cru/platform/graph/font.hpp" -#include "cru/platform/graph/painter.hpp" -#include "cru/platform/native/ui_application.hpp" -#include "cru/ui/control.hpp" -#include "cru/ui/render/canvas_render_object.hpp" -#include "cru/ui/render/text_render_object.hpp" - -#include - -namespace cru::ui::controls { -using platform::graph::ITextLayout; - -constexpr float caret_width = 2; -constexpr long long caret_blink_duration = 500; - -TextControlService::TextControlService(Control* control, - ITextControl* text_control) - : control_(control), text_control_(text_control) {} - -TextControlService::~TextControlService() { - if (enable_ && caret_visible_) TearDownCaretTimer(); -} - -void TextControlService::SetEnabled(bool enable) { - if (enable == enable_) return; - if (enable) { - AbortSelection(); - SetupHandlers(); - if (caret_visible_) { - SetupCaretTimer(); - } - } else { - event_revoker_guards_.clear(); - if (caret_visible_) { - TearDownCaretTimer(); - } - } -} - -void TextControlService::SetCaretVisible(bool visible) { - if (visible == caret_visible_) return; - - if (enable_) { - if (visible) { - SetupCaretTimer(); - } - } else { - TearDownCaretTimer(); - } -} // namespace cru::ui::controls - -void TextControlService::DrawCaret(platform::graph::IPainter* painter) { - if (caret_show_) { - const auto text_render_object = text_control_->GetTextRenderObject(); - const auto point = text_render_object->TextSingleRect( - caret_position_, false); // Maybe cache the result??? - painter->FillRectangle( - Rect{point, - Size{caret_width, text_render_object->GetFont()->GetFontSize()}}, - text_control_->GetCaretBrush()); - } -} - -void TextControlService::AbortSelection() { - if (select_down_button_.has_value()) { - control_->ReleaseMouse(); - select_down_button_ = std::nullopt; - } - text_control_->GetTextRenderObject()->SetSelectionRange(std::nullopt); -} - -void TextControlService::SetupCaretTimer() { -#ifdef CRU_DEBUG - assert(!caret_timer_set_); - caret_timer_set_ = true; -#endif - caret_timer_tag_ = - platform::native::IUiApplication::GetInstance()->SetInterval( - std::chrono::milliseconds(caret_blink_duration), [this] { - this->caret_show_ = !this->caret_show_; - this->text_control_->GetCaretRenderObject()->InvalidatePaint(); - }); -} - -void TextControlService::TearDownCaretTimer() { -#ifdef CRU_DEBUG - assert(!caret_timer_set_); - caret_timer_set_ = false; -#endif - platform::native::IUiApplication::GetInstance()->CancelTimer( - caret_timer_tag_); -} - -void TextControlService::SetupHandlers() { - assert(event_revoker_guards_.empty()); - event_revoker_guards_.push_back( - EventRevokerGuard{control_->MouseMoveEvent()->Direct()->AddHandler( - std::bind(&TextControlService::MouseMoveHandler, this, - std::placeholders::_1))}); - event_revoker_guards_.push_back( - EventRevokerGuard{control_->MouseDownEvent()->Direct()->AddHandler( - std::bind(&TextControlService::MouseDownHandler, this, - std::placeholders::_1))}); - event_revoker_guards_.push_back(EventRevokerGuard{ - control_->MouseUpEvent()->Direct()->AddHandler(std::bind( - &TextControlService::MouseUpHandler, this, std::placeholders::_1))}); - event_revoker_guards_.push_back( - EventRevokerGuard{control_->LoseFocusEvent()->Direct()->AddHandler( - std::bind(&TextControlService::LoseFocusHandler, this, - std::placeholders::_1))}); -} - -void TextControlService::MouseMoveHandler(event::MouseEventArgs& args) { - if (this->select_down_button_.has_value()) { - const auto text_render_object = this->text_control_->GetTextRenderObject(); - const auto result = text_render_object->TextHitTest( - text_render_object->FromRootToContent(args.GetPoint())); - const auto position = result.position + (result.trailing ? 1 : 0); - this->caret_position_ = position; - log::Debug( - "TextControlService: Text selection changed on mouse move, range: {}, " - "{}.", - position, this->select_start_position_); - this->text_control_->GetTextRenderObject()->SetSelectionRange( - TextRange::FromTwoSides( - static_cast(position), - static_cast(this->select_start_position_))); - this->text_control_->GetTextRenderObject()->InvalidatePaint(); - this->text_control_->GetCaretRenderObject()->InvalidatePaint(); - } -} - -void TextControlService::MouseDownHandler(event::MouseButtonEventArgs& args) { - if (this->select_down_button_.has_value()) { - return; - } else { - if (!this->control_->CaptureMouse()) return; - if (!this->control_->RequestFocus()) return; - const auto text_render_object = this->text_control_->GetTextRenderObject(); - this->select_down_button_ = args.GetButton(); - const auto result = text_render_object->TextHitTest( - text_render_object->FromRootToContent(args.GetPoint())); - const auto position = result.position + (result.trailing ? 1 : 0); - this->select_start_position_ = position; - log::Debug("TextControlService: Begin to select text, start position: {}.", - position); - } -} - -void TextControlService::MouseUpHandler(event::MouseButtonEventArgs& args) { - if (this->select_down_button_.has_value() && - this->select_down_button_.value() == args.GetButton()) { - this->control_->ReleaseMouse(); - this->select_down_button_ = std::nullopt; - log::Debug("TextControlService: End selecting text."); - } -} - -void TextControlService::LoseFocusHandler(event::FocusChangeEventArgs& args) { - if (!args.IsWindow()) this->AbortSelection(); -} -} // namespace cru::ui::controls diff --git a/src/ui/controls/text_control_service.hpp b/src/ui/controls/text_control_service.hpp new file mode 100644 index 00000000..3885242a --- /dev/null +++ b/src/ui/controls/text_control_service.hpp @@ -0,0 +1,242 @@ +#pragma once +#include "cru/common/logger.hpp" +#include "cru/platform/graph/font.hpp" +#include "cru/platform/graph/painter.hpp" +#include "cru/platform/native/ui_application.hpp" +#include "cru/ui/control.hpp" +#include "cru/ui/render/canvas_render_object.hpp" +#include "cru/ui/render/text_render_object.hpp" +#include "cru/ui/ui_event.hpp" + +namespace cru::ui::controls { +constexpr float caret_width = 2; +constexpr long long caret_blink_duration = 500; + +// TControl should inherits `Control` and has following methods: +// ``` +// render::TextRenderObject* GetTextRenderObject(); +// render::CanvasRenderObject* GetCaretRenderObject(); +// platform::graph::IBrush* GetCaretBrush(); +// ``` +template +class TextControlService : public Object { + public: + TextControlService(TControl* control); + + CRU_DELETE_COPY(TextControlService) + CRU_DELETE_MOVE(TextControlService) + + ~TextControlService(); + + public: + bool IsEnabled() { return enable_; } + void SetEnabled(bool enable); + + int GetCaretPosition() { return caret_position_; } + void SetCaretPosition(int position) { caret_position_ = position; } + + bool IsCaretVisible() { return caret_visible_; } + void SetCaretVisible(bool visible); + + // please set correct offset before calling this + void DrawCaret(platform::graph::IPainter* painter); + + private: + void AbortSelection(); + + void SetupCaretTimer(); + void TearDownCaretTimer(); + + void SetupHandlers(); + + void MouseMoveHandler(event::MouseEventArgs& args); + void MouseDownHandler(event::MouseButtonEventArgs& args); + void MouseUpHandler(event::MouseButtonEventArgs& args); + void LoseFocusHandler(event::FocusChangeEventArgs& args); + + private: + TControl* control_; + std::vector event_revoker_guards_; + + bool enable_ = false; + + bool caret_visible_ = false; + int caret_position_ = 0; +#ifdef CRU_DEBUG + bool caret_timer_set_ = false; +#endif + unsigned long caret_timer_tag_; + // this is used for blinking of caret + bool caret_show_ = true; + + // nullopt means not selecting + std::optional select_down_button_; + + // before the char + int select_start_position_; +}; + +template +TextControlService::TextControlService(TControl* control) + : control_(control) {} + +template +TextControlService::~TextControlService() { + if (enable_ && caret_visible_) TearDownCaretTimer(); +} + +template +void TextControlService::SetEnabled(bool enable) { + if (enable == enable_) return; + if (enable) { + AbortSelection(); + SetupHandlers(); + if (caret_visible_) { + SetupCaretTimer(); + } + } else { + event_revoker_guards_.clear(); + if (caret_visible_) { + TearDownCaretTimer(); + } + } +} + +template +void TextControlService::SetCaretVisible(bool visible) { + if (visible == caret_visible_) return; + + if (enable_) { + if (visible) { + SetupCaretTimer(); + } + } else { + TearDownCaretTimer(); + } +} // namespace cru::ui::controls + +template +void TextControlService::DrawCaret( + platform::graph::IPainter* painter) { + if (caret_show_) { + const auto text_render_object = control_->GetTextRenderObject(); + const auto point = text_render_object->TextSingleRect( + caret_position_, false); // Maybe cache the result??? + painter->FillRectangle( + Rect{point, + Size{caret_width, text_render_object->GetFont()->GetFontSize()}}, + control_->GetCaretBrush()); + } +} + +template +void TextControlService::AbortSelection() { + if (select_down_button_.has_value()) { + control_->ReleaseMouse(); + select_down_button_ = std::nullopt; + } + control_->GetTextRenderObject()->SetSelectionRange(std::nullopt); +} + +template +void TextControlService::SetupCaretTimer() { +#ifdef CRU_DEBUG + assert(!caret_timer_set_); + caret_timer_set_ = true; +#endif + caret_timer_tag_ = + platform::native::IUiApplication::GetInstance()->SetInterval( + std::chrono::milliseconds(caret_blink_duration), [this] { + this->caret_show_ = !this->caret_show_; + this->control_->GetCaretRenderObject()->InvalidatePaint(); + }); +} + +template +void TextControlService::TearDownCaretTimer() { +#ifdef CRU_DEBUG + assert(!caret_timer_set_); + caret_timer_set_ = false; +#endif + platform::native::IUiApplication::GetInstance()->CancelTimer( + caret_timer_tag_); +} + +template +void TextControlService::SetupHandlers() { + assert(event_revoker_guards_.empty()); + event_revoker_guards_.push_back( + EventRevokerGuard{control_->MouseMoveEvent()->Direct()->AddHandler( + std::bind(&TextControlService::MouseMoveHandler, this, + std::placeholders::_1))}); + event_revoker_guards_.push_back( + EventRevokerGuard{control_->MouseDownEvent()->Direct()->AddHandler( + std::bind(&TextControlService::MouseDownHandler, this, + std::placeholders::_1))}); + event_revoker_guards_.push_back(EventRevokerGuard{ + control_->MouseUpEvent()->Direct()->AddHandler(std::bind( + &TextControlService::MouseUpHandler, this, std::placeholders::_1))}); + event_revoker_guards_.push_back( + EventRevokerGuard{control_->LoseFocusEvent()->Direct()->AddHandler( + std::bind(&TextControlService::LoseFocusHandler, this, + std::placeholders::_1))}); +} + +template +void TextControlService::MouseMoveHandler( + event::MouseEventArgs& args) { + if (this->select_down_button_.has_value()) { + const auto text_render_object = this->control_->GetTextRenderObject(); + const auto result = text_render_object->TextHitTest( + text_render_object->FromRootToContent(args.GetPoint())); + const auto position = result.position + (result.trailing ? 1 : 0); + this->caret_position_ = position; + log::Debug( + "TextControlService: Text selection changed on mouse move, range: {}, " + "{}.", + position, this->select_start_position_); + this->control_->GetTextRenderObject()->SetSelectionRange( + TextRange::FromTwoSides( + static_cast(position), + static_cast(this->select_start_position_))); + this->control_->GetTextRenderObject()->InvalidatePaint(); + this->control_->GetCaretRenderObject()->InvalidatePaint(); + } +} + +template +void TextControlService::MouseDownHandler( + event::MouseButtonEventArgs& args) { + if (this->select_down_button_.has_value()) { + return; + } else { + if (!this->control_->CaptureMouse()) return; + if (!this->control_->RequestFocus()) return; + const auto text_render_object = this->control_->GetTextRenderObject(); + this->select_down_button_ = args.GetButton(); + const auto result = text_render_object->TextHitTest( + text_render_object->FromRootToContent(args.GetPoint())); + const auto position = result.position + (result.trailing ? 1 : 0); + this->select_start_position_ = position; + log::Debug("TextControlService: Begin to select text, start position: {}.", + position); + } +} + +template +void TextControlService::MouseUpHandler( + event::MouseButtonEventArgs& args) { + if (this->select_down_button_.has_value() && + this->select_down_button_.value() == args.GetButton()) { + this->control_->ReleaseMouse(); + this->select_down_button_ = std::nullopt; + log::Debug("TextControlService: End selecting text."); + } +} + +template +void TextControlService::LoseFocusHandler( + event::FocusChangeEventArgs& args) { + if (!args.IsWindow()) this->AbortSelection(); +} +} // namespace cru::ui::controls -- cgit v1.2.3