From d86a71f79afe0e4dac768f61d6bff690567aca5b Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 24 May 2020 01:40:02 +0800 Subject: ... --- src/ui/controls/FlexLayout.cpp | 71 ++++++++++ src/ui/controls/StackLayout.cpp | 27 ++++ src/ui/controls/TextBlock.cpp | 44 ++++++ src/ui/controls/TextBox.cpp | 70 ++++++++++ src/ui/controls/TextControlService.hpp | 227 +++++++++++++++++++++++++++++++ src/ui/controls/button.cpp | 16 +-- src/ui/controls/container.cpp | 6 +- src/ui/controls/flex_layout.cpp | 71 ---------- src/ui/controls/stack_layout.cpp | 27 ---- src/ui/controls/text_block.cpp | 44 ------ src/ui/controls/text_box.cpp | 70 ---------- src/ui/controls/text_control_service.hpp | 227 ------------------------------- 12 files changed, 450 insertions(+), 450 deletions(-) create mode 100644 src/ui/controls/FlexLayout.cpp create mode 100644 src/ui/controls/StackLayout.cpp create mode 100644 src/ui/controls/TextBlock.cpp create mode 100644 src/ui/controls/TextBox.cpp create mode 100644 src/ui/controls/TextControlService.hpp delete mode 100644 src/ui/controls/flex_layout.cpp delete mode 100644 src/ui/controls/stack_layout.cpp delete mode 100644 src/ui/controls/text_block.cpp delete mode 100644 src/ui/controls/text_box.cpp delete mode 100644 src/ui/controls/text_control_service.hpp (limited to 'src/ui/controls') diff --git a/src/ui/controls/FlexLayout.cpp b/src/ui/controls/FlexLayout.cpp new file mode 100644 index 00000000..25f30558 --- /dev/null +++ b/src/ui/controls/FlexLayout.cpp @@ -0,0 +1,71 @@ +#include "cru/ui/controls/FlexLayout.hpp" + +#include "cru/ui/render/FlexLayoutRenderObject.hpp" + +namespace cru::ui::controls { +using render::FlexLayoutRenderObject; + +FlexLayout::FlexLayout() { + render_object_.reset(new FlexLayoutRenderObject()); + render_object_->SetAttachedControl(this); +} + +FlexLayout::~FlexLayout() = default; + +render::RenderObject* FlexLayout::GetRenderObject() const { + return render_object_.get(); +} + +namespace { +int FindPosition(render::RenderObject* parent, render::RenderObject* child) { + const auto& render_objects = parent->GetChildren(); + const auto find_result = + std::find(render_objects.cbegin(), render_objects.cend(), child); + if (find_result == render_objects.cend()) { + throw std::logic_error("Control is not a child of FlexLayout."); + } + return static_cast(find_result - render_objects.cbegin()); +} +} // namespace + +FlexChildLayoutData FlexLayout::GetChildLayoutData(Control* control) { + Expects(control); + return *render_object_->GetChildLayoutData( + FindPosition(render_object_.get(), control->GetRenderObject())); +} + +void FlexLayout::SetChildLayoutData(Control* control, + const FlexChildLayoutData& data) { + Expects(control); + *render_object_->GetChildLayoutData( + FindPosition(render_object_.get(), control->GetRenderObject())) = data; +} + +FlexMainAlignment FlexLayout::GetContentMainAlign() const { + return render_object_->GetContentMainAlign(); +} + +void FlexLayout::SetContentMainAlign(FlexMainAlignment value) { + if (value == GetContentMainAlign()) return; + render_object_->SetContentMainAlign(value); +} + +FlexDirection FlexLayout::GetFlexDirection() const { + return render_object_->GetFlexDirection(); +} + +void FlexLayout::SetFlexDirection(FlexDirection direction) { + if (direction == GetFlexDirection()) return; + render_object_->SetFlexDirection(direction); +} + +void FlexLayout::OnAddChild(Control* child, const Index position) { + render_object_->AddChild(child->GetRenderObject(), position); +} + +void FlexLayout::OnRemoveChild(Control* child, const Index position) { + CRU_UNUSED(child) + + render_object_->RemoveChild(position); +} +} // namespace cru::ui::controls diff --git a/src/ui/controls/StackLayout.cpp b/src/ui/controls/StackLayout.cpp new file mode 100644 index 00000000..ce500b79 --- /dev/null +++ b/src/ui/controls/StackLayout.cpp @@ -0,0 +1,27 @@ +#include "cru/ui/controls/StackLayout.hpp" + +#include "cru/ui/render/StackLayoutRenderObject.hpp" + +namespace cru::ui::controls { +using render::StackLayoutRenderObject; + +StackLayout::StackLayout() : render_object_(new StackLayoutRenderObject()) { + render_object_->SetAttachedControl(this); +} + +StackLayout::~StackLayout() = default; + +render::RenderObject* StackLayout::GetRenderObject() const { + return render_object_.get(); +} + +void StackLayout::OnAddChild(Control* child, const Index position) { + render_object_->AddChild(child->GetRenderObject(), position); +} + +void StackLayout::OnRemoveChild(Control* child, const Index position) { + CRU_UNUSED(child) + + render_object_->RemoveChild(position); +} +} // namespace cru::ui::controls diff --git a/src/ui/controls/TextBlock.cpp b/src/ui/controls/TextBlock.cpp new file mode 100644 index 00000000..f77e279b --- /dev/null +++ b/src/ui/controls/TextBlock.cpp @@ -0,0 +1,44 @@ +#include "cru/ui/controls/TextBlock.hpp" + +#include "cru/ui/render/CanvasRenderObject.hpp" +#include "cru/ui/render/StackLayoutRenderObject.hpp" +#include "cru/ui/render/TextRenderObject.hpp" +#include "cru/ui/UiManager.hpp" +#include "TextControlService.hpp" + +namespace cru::ui::controls { +using render::CanvasRenderObject; +using render::StackLayoutRenderObject; +using render::TextRenderObject; + +TextBlock::TextBlock() { + const auto theme_resources = UiManager::GetInstance()->GetThemeResources(); + + text_render_object_ = std::make_unique( + theme_resources->text_brush, theme_resources->default_font, + theme_resources->text_selection_brush, theme_resources->caret_brush); + + text_render_object_->SetAttachedControl(this); + + service_ = std::make_unique>(this); + service_->SetEnabled(true); +} + +TextBlock::~TextBlock() = default; + +render::RenderObject* TextBlock::GetRenderObject() const { + return text_render_object_.get(); +} + +std::string TextBlock::GetText() const { + return text_render_object_->GetText(); +} + +void TextBlock::SetText(std::string text) { + text_render_object_->SetText(std::move(text)); +} + +render::TextRenderObject* TextBlock::GetTextRenderObject() { + return text_render_object_.get(); +} +} // namespace cru::ui::controls diff --git a/src/ui/controls/TextBox.cpp b/src/ui/controls/TextBox.cpp new file mode 100644 index 00000000..64fd4c60 --- /dev/null +++ b/src/ui/controls/TextBox.cpp @@ -0,0 +1,70 @@ +#include "cru/ui/controls/TextBox.hpp" + +#include "cru/ui/render/BorderRenderObject.hpp" +#include "cru/ui/render/CanvasRenderObject.hpp" +#include "cru/ui/render/StackLayoutRenderObject.hpp" +#include "cru/ui/render/TextRenderObject.hpp" +#include "cru/ui/UiManager.hpp" +#include "TextControlService.hpp" + +namespace cru::ui::controls { +using render::BorderRenderObject; +using render::CanvasRenderObject; +using render::StackLayoutRenderObject; +using render::TextRenderObject; + +TextBox::TextBox() : border_render_object_(new BorderRenderObject()) { + const auto theme_resources = UiManager::GetInstance()->GetThemeResources(); + + border_style_ = theme_resources->text_box_border_style; + + text_render_object_ = std::make_unique( + theme_resources->text_brush, theme_resources->default_font, + theme_resources->text_selection_brush, theme_resources->caret_brush); + + border_render_object_->AddChild(text_render_object_.get(), 0); + + border_render_object_->SetAttachedControl(this); + text_render_object_->SetAttachedControl(this); + + service_ = std::make_unique>(this); + service_->SetEnabled(true); + service_->SetCaretVisible(true); + + GainFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) { + this->service_->SetEnabled(true); + this->UpdateBorderStyle(); + }); + + LoseFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) { + this->service_->SetEnabled(false); + this->UpdateBorderStyle(); + }); +} + +TextBox::~TextBox() {} + +render::RenderObject* TextBox::GetRenderObject() const { + return border_render_object_.get(); +} + +render::TextRenderObject* TextBox::GetTextRenderObject() { + return text_render_object_.get(); +} + +const TextBoxBorderStyle& TextBox::GetBorderStyle() { return border_style_; } + +void TextBox::SetBorderStyle(TextBoxBorderStyle border_style) { + border_style_ = std::move(border_style); +} + +void TextBox::OnMouseHoverChange(bool) { UpdateBorderStyle(); } + +void TextBox::UpdateBorderStyle() { + const auto focus = HasFocus(); + const auto hover = IsMouseOver(); + border_render_object_->SetBorderStyle( + focus ? (hover ? border_style_.focus_hover : border_style_.focus) + : (hover ? border_style_.hover : border_style_.normal)); +} +} // namespace cru::ui::controls diff --git a/src/ui/controls/TextControlService.hpp b/src/ui/controls/TextControlService.hpp new file mode 100644 index 00000000..ad0db343 --- /dev/null +++ b/src/ui/controls/TextControlService.hpp @@ -0,0 +1,227 @@ +#pragma once +#include "../Helper.hpp" +#include "cru/common/Logger.hpp" +#include "cru/platform/graph/Font.hpp" +#include "cru/platform/graph/Painter.hpp" +#include "cru/platform/native/UiApplication.hpp" +#include "cru/ui/Control.hpp" +#include "cru/ui/render/CanvasRenderObject.hpp" +#include "cru/ui/render/TextRenderObject.hpp" +#include "cru/ui/UiEvent.hpp" + +namespace cru::ui::controls { +constexpr int k_default_caret_blink_duration = 500; + +// TControl should inherits `Control` and has following methods: +// ``` +// render::TextRenderObject* GetTextRenderObject(); +// ``` +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); + + bool IsCaretVisible() { return caret_visible_; } + void SetCaretVisible(bool visible); + + int GetCaretBlinkDuration() { return caret_blink_duration_; } + void SetCaretBlinkDuration(int milliseconds); + + private: + void AbortSelection(); + + void SetupCaret(); + void TearDownCaret(); + + 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; + long long caret_timer_id_ = -1; + int caret_blink_duration_ = k_default_caret_blink_duration; + + // 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() { + const auto application = GetUiApplication(); + // Don't call TearDownCaret, because it use text render object of control, + // which may be destroyed already. + application->CancelTimer(this->caret_timer_id_); +} + +template +void TextControlService::SetEnabled(bool enable) { + if (enable == this->enable_) return; + if (enable) { + this->SetupHandlers(); + if (this->caret_visible_) { + this->SetupCaret(); + } + } else { + this->AbortSelection(); + this->event_revoker_guards_.clear(); + this->TearDownCaret(); + } +} + +template +void TextControlService::SetCaretVisible(bool visible) { + if (visible == this->caret_visible_) return; + + this->caret_visible_ = visible; + + if (this->enable_) { + if (visible) { + this->SetupCaret(); + } else { + this->TearDownCaret(); + } + } +} + +template +void TextControlService::SetCaretBlinkDuration(int milliseconds) { + if (this->caret_blink_duration_ == milliseconds) return; + + if (this->enable_ && this->caret_visible_) { + this->TearDownCaret(); + this->SetupCaret(); + } +} + +template +void TextControlService::AbortSelection() { + if (this->select_down_button_.has_value()) { + this->control_->ReleaseMouse(); + this->select_down_button_ = std::nullopt; + } + this->control_->GetTextRenderObject()->SetSelectionRange(std::nullopt); +} + +template +void TextControlService::SetupCaret() { + const auto application = GetUiApplication(); + + // Cancel first anyhow for safety. + application->CancelTimer(this->caret_timer_id_); + + this->control_->GetTextRenderObject()->SetDrawCaret(true); + this->caret_timer_id_ = application->SetInterval( + std::chrono::milliseconds(this->caret_blink_duration_), + [this] { this->control_->GetTextRenderObject()->ToggleDrawCaret(); }); +} + +template +void TextControlService::TearDownCaret() { + const auto application = GetUiApplication(); + application->CancelTimer(this->caret_timer_id_); + this->control_->GetTextRenderObject()->SetDrawCaret(false); +} + +template +void TextControlService::SetupHandlers() { + Expects(event_revoker_guards_.empty()); + this->event_revoker_guards_.push_back( + EventRevokerGuard{control_->MouseMoveEvent()->Direct()->AddHandler( + std::bind(&TextControlService::MouseMoveHandler, this, + std::placeholders::_1))}); + this->event_revoker_guards_.push_back( + EventRevokerGuard{control_->MouseDownEvent()->Direct()->AddHandler( + std::bind(&TextControlService::MouseDownHandler, this, + std::placeholders::_1))}); + this->event_revoker_guards_.push_back(EventRevokerGuard{ + control_->MouseUpEvent()->Direct()->AddHandler(std::bind( + &TextControlService::MouseUpHandler, this, std::placeholders::_1))}); + this->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); + 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_))); + text_render_object->SetCaretPosition(position); + } +} + +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); + text_render_object->SetSelectionRange(std::nullopt); + text_render_object->SetCaretPosition(position); + 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 diff --git a/src/ui/controls/button.cpp b/src/ui/controls/button.cpp index 7dd087ba..6f6af878 100644 --- a/src/ui/controls/button.cpp +++ b/src/ui/controls/button.cpp @@ -1,13 +1,13 @@ -#include "cru/ui/controls/button.hpp" +#include "cru/ui/controls/Button.hpp" #include -#include "../helper.hpp" -#include "cru/platform/graph/brush.hpp" -#include "cru/platform/native/cursor.hpp" -#include "cru/platform/native/ui_application.hpp" -#include "cru/ui/render/border_render_object.hpp" -#include "cru/ui/ui_manager.hpp" -#include "cru/ui/window.hpp" +#include "../Helper.hpp" +#include "cru/platform/graph/Brush.hpp" +#include "cru/platform/native/Cursor.hpp" +#include "cru/platform/native/UiApplication.hpp" +#include "cru/ui/render/BorderRenderObject.hpp" +#include "cru/ui/UiManager.hpp" +#include "cru/ui/Window.hpp" namespace cru::ui::controls { using cru::platform::native::SystemCursorType; diff --git a/src/ui/controls/container.cpp b/src/ui/controls/container.cpp index 84582d80..de58ee64 100644 --- a/src/ui/controls/container.cpp +++ b/src/ui/controls/container.cpp @@ -1,7 +1,7 @@ -#include "cru/ui/controls/container.hpp" +#include "cru/ui/controls/Container.hpp" -#include "cru/platform/graph/factory.hpp" -#include "cru/ui/render/border_render_object.hpp" +#include "cru/platform/graph/Factory.hpp" +#include "cru/ui/render/BorderRenderObject.hpp" namespace cru::ui::controls { Container::Container() { diff --git a/src/ui/controls/flex_layout.cpp b/src/ui/controls/flex_layout.cpp deleted file mode 100644 index 5412164a..00000000 --- a/src/ui/controls/flex_layout.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "cru/ui/controls/flex_layout.hpp" - -#include "cru/ui/render/flex_layout_render_object.hpp" - -namespace cru::ui::controls { -using render::FlexLayoutRenderObject; - -FlexLayout::FlexLayout() { - render_object_.reset(new FlexLayoutRenderObject()); - render_object_->SetAttachedControl(this); -} - -FlexLayout::~FlexLayout() = default; - -render::RenderObject* FlexLayout::GetRenderObject() const { - return render_object_.get(); -} - -namespace { -int FindPosition(render::RenderObject* parent, render::RenderObject* child) { - const auto& render_objects = parent->GetChildren(); - const auto find_result = - std::find(render_objects.cbegin(), render_objects.cend(), child); - if (find_result == render_objects.cend()) { - throw std::logic_error("Control is not a child of FlexLayout."); - } - return static_cast(find_result - render_objects.cbegin()); -} -} // namespace - -FlexChildLayoutData FlexLayout::GetChildLayoutData(Control* control) { - Expects(control); - return *render_object_->GetChildLayoutData( - FindPosition(render_object_.get(), control->GetRenderObject())); -} - -void FlexLayout::SetChildLayoutData(Control* control, - const FlexChildLayoutData& data) { - Expects(control); - *render_object_->GetChildLayoutData( - FindPosition(render_object_.get(), control->GetRenderObject())) = data; -} - -FlexMainAlignment FlexLayout::GetContentMainAlign() const { - return render_object_->GetContentMainAlign(); -} - -void FlexLayout::SetContentMainAlign(FlexMainAlignment value) { - if (value == GetContentMainAlign()) return; - render_object_->SetContentMainAlign(value); -} - -FlexDirection FlexLayout::GetFlexDirection() const { - return render_object_->GetFlexDirection(); -} - -void FlexLayout::SetFlexDirection(FlexDirection direction) { - if (direction == GetFlexDirection()) return; - render_object_->SetFlexDirection(direction); -} - -void FlexLayout::OnAddChild(Control* child, const Index position) { - render_object_->AddChild(child->GetRenderObject(), position); -} - -void FlexLayout::OnRemoveChild(Control* child, const Index position) { - CRU_UNUSED(child) - - render_object_->RemoveChild(position); -} -} // namespace cru::ui::controls diff --git a/src/ui/controls/stack_layout.cpp b/src/ui/controls/stack_layout.cpp deleted file mode 100644 index 47511f33..00000000 --- a/src/ui/controls/stack_layout.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "cru/ui/controls/stack_layout.hpp" - -#include "cru/ui/render/stack_layout_render_object.hpp" - -namespace cru::ui::controls { -using render::StackLayoutRenderObject; - -StackLayout::StackLayout() : render_object_(new StackLayoutRenderObject()) { - render_object_->SetAttachedControl(this); -} - -StackLayout::~StackLayout() = default; - -render::RenderObject* StackLayout::GetRenderObject() const { - return render_object_.get(); -} - -void StackLayout::OnAddChild(Control* child, const Index position) { - render_object_->AddChild(child->GetRenderObject(), position); -} - -void StackLayout::OnRemoveChild(Control* child, const Index position) { - CRU_UNUSED(child) - - render_object_->RemoveChild(position); -} -} // namespace cru::ui::controls diff --git a/src/ui/controls/text_block.cpp b/src/ui/controls/text_block.cpp deleted file mode 100644 index a3ec9f54..00000000 --- a/src/ui/controls/text_block.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "cru/ui/controls/text_block.hpp" - -#include "cru/ui/render/canvas_render_object.hpp" -#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; -using render::StackLayoutRenderObject; -using render::TextRenderObject; - -TextBlock::TextBlock() { - const auto theme_resources = UiManager::GetInstance()->GetThemeResources(); - - text_render_object_ = std::make_unique( - theme_resources->text_brush, theme_resources->default_font, - theme_resources->text_selection_brush, theme_resources->caret_brush); - - text_render_object_->SetAttachedControl(this); - - service_ = std::make_unique>(this); - service_->SetEnabled(true); -} - -TextBlock::~TextBlock() = default; - -render::RenderObject* TextBlock::GetRenderObject() const { - return text_render_object_.get(); -} - -std::string TextBlock::GetText() const { - return text_render_object_->GetText(); -} - -void TextBlock::SetText(std::string text) { - text_render_object_->SetText(std::move(text)); -} - -render::TextRenderObject* TextBlock::GetTextRenderObject() { - return text_render_object_.get(); -} -} // namespace cru::ui::controls diff --git a/src/ui/controls/text_box.cpp b/src/ui/controls/text_box.cpp deleted file mode 100644 index 8b7dc692..00000000 --- a/src/ui/controls/text_box.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "cru/ui/controls/text_box.hpp" - -#include "cru/ui/render/border_render_object.hpp" -#include "cru/ui/render/canvas_render_object.hpp" -#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::BorderRenderObject; -using render::CanvasRenderObject; -using render::StackLayoutRenderObject; -using render::TextRenderObject; - -TextBox::TextBox() : border_render_object_(new BorderRenderObject()) { - const auto theme_resources = UiManager::GetInstance()->GetThemeResources(); - - border_style_ = theme_resources->text_box_border_style; - - text_render_object_ = std::make_unique( - theme_resources->text_brush, theme_resources->default_font, - theme_resources->text_selection_brush, theme_resources->caret_brush); - - border_render_object_->AddChild(text_render_object_.get(), 0); - - border_render_object_->SetAttachedControl(this); - text_render_object_->SetAttachedControl(this); - - service_ = std::make_unique>(this); - service_->SetEnabled(true); - service_->SetCaretVisible(true); - - GainFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) { - this->service_->SetEnabled(true); - this->UpdateBorderStyle(); - }); - - LoseFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) { - this->service_->SetEnabled(false); - this->UpdateBorderStyle(); - }); -} - -TextBox::~TextBox() {} - -render::RenderObject* TextBox::GetRenderObject() const { - return border_render_object_.get(); -} - -render::TextRenderObject* TextBox::GetTextRenderObject() { - return text_render_object_.get(); -} - -const TextBoxBorderStyle& TextBox::GetBorderStyle() { return border_style_; } - -void TextBox::SetBorderStyle(TextBoxBorderStyle border_style) { - border_style_ = std::move(border_style); -} - -void TextBox::OnMouseHoverChange(bool) { UpdateBorderStyle(); } - -void TextBox::UpdateBorderStyle() { - const auto focus = HasFocus(); - const auto hover = IsMouseOver(); - border_render_object_->SetBorderStyle( - focus ? (hover ? border_style_.focus_hover : border_style_.focus) - : (hover ? border_style_.hover : border_style_.normal)); -} -} // namespace cru::ui::controls diff --git a/src/ui/controls/text_control_service.hpp b/src/ui/controls/text_control_service.hpp deleted file mode 100644 index 626423bf..00000000 --- a/src/ui/controls/text_control_service.hpp +++ /dev/null @@ -1,227 +0,0 @@ -#pragma once -#include "../helper.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 "cru/ui/ui_event.hpp" - -namespace cru::ui::controls { -constexpr int k_default_caret_blink_duration = 500; - -// TControl should inherits `Control` and has following methods: -// ``` -// render::TextRenderObject* GetTextRenderObject(); -// ``` -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); - - bool IsCaretVisible() { return caret_visible_; } - void SetCaretVisible(bool visible); - - int GetCaretBlinkDuration() { return caret_blink_duration_; } - void SetCaretBlinkDuration(int milliseconds); - - private: - void AbortSelection(); - - void SetupCaret(); - void TearDownCaret(); - - 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; - long long caret_timer_id_ = -1; - int caret_blink_duration_ = k_default_caret_blink_duration; - - // 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() { - const auto application = GetUiApplication(); - // Don't call TearDownCaret, because it use text render object of control, - // which may be destroyed already. - application->CancelTimer(this->caret_timer_id_); -} - -template -void TextControlService::SetEnabled(bool enable) { - if (enable == this->enable_) return; - if (enable) { - this->SetupHandlers(); - if (this->caret_visible_) { - this->SetupCaret(); - } - } else { - this->AbortSelection(); - this->event_revoker_guards_.clear(); - this->TearDownCaret(); - } -} - -template -void TextControlService::SetCaretVisible(bool visible) { - if (visible == this->caret_visible_) return; - - this->caret_visible_ = visible; - - if (this->enable_) { - if (visible) { - this->SetupCaret(); - } else { - this->TearDownCaret(); - } - } -} - -template -void TextControlService::SetCaretBlinkDuration(int milliseconds) { - if (this->caret_blink_duration_ == milliseconds) return; - - if (this->enable_ && this->caret_visible_) { - this->TearDownCaret(); - this->SetupCaret(); - } -} - -template -void TextControlService::AbortSelection() { - if (this->select_down_button_.has_value()) { - this->control_->ReleaseMouse(); - this->select_down_button_ = std::nullopt; - } - this->control_->GetTextRenderObject()->SetSelectionRange(std::nullopt); -} - -template -void TextControlService::SetupCaret() { - const auto application = GetUiApplication(); - - // Cancel first anyhow for safety. - application->CancelTimer(this->caret_timer_id_); - - this->control_->GetTextRenderObject()->SetDrawCaret(true); - this->caret_timer_id_ = application->SetInterval( - std::chrono::milliseconds(this->caret_blink_duration_), - [this] { this->control_->GetTextRenderObject()->ToggleDrawCaret(); }); -} - -template -void TextControlService::TearDownCaret() { - const auto application = GetUiApplication(); - application->CancelTimer(this->caret_timer_id_); - this->control_->GetTextRenderObject()->SetDrawCaret(false); -} - -template -void TextControlService::SetupHandlers() { - Expects(event_revoker_guards_.empty()); - this->event_revoker_guards_.push_back( - EventRevokerGuard{control_->MouseMoveEvent()->Direct()->AddHandler( - std::bind(&TextControlService::MouseMoveHandler, this, - std::placeholders::_1))}); - this->event_revoker_guards_.push_back( - EventRevokerGuard{control_->MouseDownEvent()->Direct()->AddHandler( - std::bind(&TextControlService::MouseDownHandler, this, - std::placeholders::_1))}); - this->event_revoker_guards_.push_back(EventRevokerGuard{ - control_->MouseUpEvent()->Direct()->AddHandler(std::bind( - &TextControlService::MouseUpHandler, this, std::placeholders::_1))}); - this->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); - 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_))); - text_render_object->SetCaretPosition(position); - } -} - -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); - text_render_object->SetSelectionRange(std::nullopt); - text_render_object->SetCaretPosition(position); - 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