diff options
-rw-r--r-- | .vscode/settings.json | 12 | ||||
-rw-r--r-- | include/cru/ui/UiEvent.hpp | 4 | ||||
-rw-r--r-- | include/cru/ui/controls/TextBlock.hpp | 3 | ||||
-rw-r--r-- | include/cru/ui/controls/TextBox.hpp | 4 | ||||
-rw-r--r-- | include/cru/ui/render/Base.hpp | 1 | ||||
-rw-r--r-- | src/ui/controls/TextBlock.cpp | 6 | ||||
-rw-r--r-- | src/ui/controls/TextBox.cpp | 20 | ||||
-rw-r--r-- | src/ui/controls/TextControlService.hpp | 306 |
8 files changed, 177 insertions, 179 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json index a53c1bbb..a2ff60cb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -76,7 +76,17 @@ "cstdarg": "cpp", "locale": "cpp", "xlocbuf": "cpp", - "xlocmes": "cpp" + "xlocmes": "cpp", + "codecvt": "cpp", + "deque": "cpp", + "filesystem": "cpp", + "fstream": "cpp", + "future": "cpp", + "random": "cpp", + "regex": "cpp", + "stack": "cpp", + "unordered_set": "cpp", + "variant": "cpp" }, "cmake.configureSettings": { "CMAKE_TOOLCHAIN_FILE": "${workspaceFolder}/vcpkg/scripts/buildsystems/vcpkg.cmake", diff --git a/include/cru/ui/UiEvent.hpp b/include/cru/ui/UiEvent.hpp index 29292d75..79d0f7e3 100644 --- a/include/cru/ui/UiEvent.hpp +++ b/include/cru/ui/UiEvent.hpp @@ -52,7 +52,9 @@ class RoutedEvent { static_assert(!std::is_reference_v<TEventArgs>, "TEventArgs must not be reference."); - using EventArgs = TEventArgs; + using RawEventArgs = TEventArgs; + using IEventType = IEvent<TEventArgs&>; + using EventArgs = typename IEventType::EventArgs; RoutedEvent() = default; RoutedEvent(const RoutedEvent& other) = delete; diff --git a/include/cru/ui/controls/TextBlock.hpp b/include/cru/ui/controls/TextBlock.hpp index dd8b40b4..1b1b4a5c 100644 --- a/include/cru/ui/controls/TextBlock.hpp +++ b/include/cru/ui/controls/TextBlock.hpp @@ -28,7 +28,8 @@ class TextBlock : public NoChildControl { std::string GetText() const; void SetText(std::string text); - render::TextRenderObject* GetTextRenderObject(); + gsl::not_null<render::TextRenderObject*> GetTextRenderObject(); + render::ScrollRenderObject* GetScrollRenderObject() { return nullptr; } private: std::unique_ptr<render::TextRenderObject> text_render_object_; diff --git a/include/cru/ui/controls/TextBox.hpp b/include/cru/ui/controls/TextBox.hpp index 2f7a12b6..3d4de7c0 100644 --- a/include/cru/ui/controls/TextBox.hpp +++ b/include/cru/ui/controls/TextBox.hpp @@ -27,7 +27,8 @@ class TextBox : public NoChildControl { render::RenderObject* GetRenderObject() const override; - render::TextRenderObject* GetTextRenderObject(); + gsl::not_null<render::TextRenderObject*> GetTextRenderObject(); + render::ScrollRenderObject* GetScrollRenderObject(); const TextBoxBorderStyle& GetBorderStyle(); void SetBorderStyle(TextBoxBorderStyle border_style); @@ -40,6 +41,7 @@ class TextBox : public NoChildControl { private: std::unique_ptr<render::BorderRenderObject> border_render_object_; + std::unique_ptr<render::ScrollRenderObject> scroll_render_object_; std::unique_ptr<render::TextRenderObject> text_render_object_; TextBoxBorderStyle border_style_; diff --git a/include/cru/ui/render/Base.hpp b/include/cru/ui/render/Base.hpp index c2af5e99..801d58bd 100644 --- a/include/cru/ui/render/Base.hpp +++ b/include/cru/ui/render/Base.hpp @@ -6,6 +6,7 @@ class RenderObject; class BorderRenderObject; class CanvasRenderObject; class FlexLayoutRenderObject; +class ScrollRenderObject; class StackLayoutRenderObject; class TextRenderObject; class WindowRenderObject; diff --git a/src/ui/controls/TextBlock.cpp b/src/ui/controls/TextBlock.cpp index f77e279b..5ec15796 100644 --- a/src/ui/controls/TextBlock.cpp +++ b/src/ui/controls/TextBlock.cpp @@ -1,10 +1,10 @@ #include "cru/ui/controls/TextBlock.hpp" +#include "TextControlService.hpp" +#include "cru/ui/UiManager.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; @@ -38,7 +38,7 @@ void TextBlock::SetText(std::string text) { text_render_object_->SetText(std::move(text)); } -render::TextRenderObject* TextBlock::GetTextRenderObject() { +gsl::not_null<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 index 64fd4c60..b25e3bcb 100644 --- a/src/ui/controls/TextBox.cpp +++ b/src/ui/controls/TextBox.cpp @@ -1,19 +1,23 @@ #include "cru/ui/controls/TextBox.hpp" +#include "TextControlService.hpp" +#include "cru/ui/UiManager.hpp" #include "cru/ui/render/BorderRenderObject.hpp" #include "cru/ui/render/CanvasRenderObject.hpp" +#include "cru/ui/render/ScrollRenderObject.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::ScrollRenderObject; using render::StackLayoutRenderObject; using render::TextRenderObject; -TextBox::TextBox() : border_render_object_(new BorderRenderObject()) { +TextBox::TextBox() + : border_render_object_(new BorderRenderObject()), + scroll_render_object_(new ScrollRenderObject()) { const auto theme_resources = UiManager::GetInstance()->GetThemeResources(); border_style_ = theme_resources->text_box_border_style; @@ -22,9 +26,11 @@ TextBox::TextBox() : border_render_object_(new BorderRenderObject()) { 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_->AddChild(scroll_render_object_.get(), 0); + scroll_render_object_->AddChild(text_render_object_.get(), 0); border_render_object_->SetAttachedControl(this); + scroll_render_object_->SetAttachedControl(this); text_render_object_->SetAttachedControl(this); service_ = std::make_unique<TextControlService<TextBox>>(this); @@ -48,10 +54,14 @@ render::RenderObject* TextBox::GetRenderObject() const { return border_render_object_.get(); } -render::TextRenderObject* TextBox::GetTextRenderObject() { +gsl::not_null<render::TextRenderObject*> TextBox::GetTextRenderObject() { return text_render_object_.get(); } +render::ScrollRenderObject* TextBox::GetScrollRenderObject() { + return scroll_render_object_.get(); +} + const TextBoxBorderStyle& TextBox::GetBorderStyle() { return border_style_; } void TextBox::SetBorderStyle(TextBoxBorderStyle border_style) { diff --git a/src/ui/controls/TextControlService.hpp b/src/ui/controls/TextControlService.hpp index c320b0c5..5fc8d987 100644 --- a/src/ui/controls/TextControlService.hpp +++ b/src/ui/controls/TextControlService.hpp @@ -14,215 +14,187 @@ constexpr int k_default_caret_blink_duration = 500; // TControl should inherits `Control` and has following methods: // ``` -// render::TextRenderObject* GetTextRenderObject(); +// gsl::not_null<render::TextRenderObject*> GetTextRenderObject(); +// render::ScrollRenderObject* GetScrollRenderObject(); // ``` template <typename TControl> class TextControlService : public Object { CRU_DEFINE_CLASS_LOG_TAG("cru::ui::controls::TextControlService") public: - TextControlService(TControl* control); + TextControlService(gsl::not_null<TControl*> control) : control_(control) {} CRU_DELETE_COPY(TextControlService) CRU_DELETE_MOVE(TextControlService) - ~TextControlService(); + ~TextControlService() override { + 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_); + } public: bool IsEnabled() { return enable_; } - void SetEnabled(bool enable); + + void 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(); + } + } bool IsCaretVisible() { return caret_visible_; } - void SetCaretVisible(bool visible); - int GetCaretBlinkDuration() { return caret_blink_duration_; } - void SetCaretBlinkDuration(int milliseconds); + void SetCaretVisible(bool visible) { + if (visible == this->caret_visible_) return; - private: - void AbortSelection(); + this->caret_visible_ = visible; - void SetupCaret(); - void TearDownCaret(); + if (this->enable_) { + if (visible) { + this->SetupCaret(); + } else { + this->TearDownCaret(); + } + } + } - void SetupHandlers(); + int GetCaretBlinkDuration() { return caret_blink_duration_; } - void MouseMoveHandler(event::MouseEventArgs& args); - void MouseDownHandler(event::MouseButtonEventArgs& args); - void MouseUpHandler(event::MouseButtonEventArgs& args); - void LoseFocusHandler(event::FocusChangeEventArgs& args); + void SetCaretBlinkDuration(int milliseconds) { + if (this->caret_blink_duration_ == milliseconds) return; - private: - TControl* control_; - std::vector<EventRevokerGuard> event_revoker_guards_; + if (this->enable_ && this->caret_visible_) { + this->TearDownCaret(); + this->SetupCaret(); + } + } - bool enable_ = false; + std::optional<TextRange> GetSelection() { + return this->control_->GetTextRenderObject()->GetSelectionRange(); + } + void SetSelection(std::optional<TextRange> selection) { + this->control_->GetTextRenderObject()->SetSelectionRange(selection); + } - bool caret_visible_ = false; - long long caret_timer_id_ = -1; - int caret_blink_duration_ = k_default_caret_blink_duration; + private: + void AbortSelection() { + if (this->select_down_button_.has_value()) { + this->control_->ReleaseMouse(); + this->select_down_button_ = std::nullopt; + } + this->control_->GetTextRenderObject()->SetSelectionRange(std::nullopt); + } - // nullopt means not selecting - std::optional<MouseButton> select_down_button_; + void SetupCaret() { + const auto application = GetUiApplication(); - // before the char - int select_start_position_; -}; + // Cancel first anyhow for safety. + application->CancelTimer(this->caret_timer_id_); -template <typename TControl> -TextControlService<TControl>::TextControlService(TControl* control) - : control_(control) {} + this->control_->GetTextRenderObject()->SetDrawCaret(true); + this->caret_timer_id_ = application->SetInterval( + std::chrono::milliseconds(this->caret_blink_duration_), + [this] { this->control_->GetTextRenderObject()->ToggleDrawCaret(); }); + } -template <typename TControl> -TextControlService<TControl>::~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_); -} + void TearDownCaret() { + const auto application = GetUiApplication(); + application->CancelTimer(this->caret_timer_id_); + this->control_->GetTextRenderObject()->SetDrawCaret(false); + } -template <typename TControl> -void TextControlService<TControl>::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 <typename TArgs> + void SetupOneHandler(event::RoutedEvent<TArgs>* (TControl::*event)(), + void (TextControlService::*handler)( + typename event::RoutedEvent<TArgs>::EventArgs)) { + this->event_revoker_guards_.push_back( + EventRevokerGuard{(this->control_->*event)()->Direct()->AddHandler( + std::bind(handler, this, std::placeholders::_1))}); } -} -template <typename TControl> -void TextControlService<TControl>::SetCaretVisible(bool visible) { - if (visible == this->caret_visible_) return; + void SetupHandlers() { + Expects(event_revoker_guards_.empty()); + + SetupOneHandler(&Control::MouseMoveEvent, + &TextControlService::MouseMoveHandler); + SetupOneHandler(&Control::MouseDownEvent, + &TextControlService::MouseDownHandler); + SetupOneHandler(&Control::MouseUpEvent, + &TextControlService::MouseUpHandler); + SetupOneHandler(&Control::LoseFocusEvent, + &TextControlService::LoseFocusHandler); + } - this->caret_visible_ = visible; + void 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::TagDebug(log_tag, + "Text selection changed on mouse move, range: {}, {}.", + position, this->select_start_position_); + this->control_->GetTextRenderObject()->SetSelectionRange( + TextRange::FromTwoSides(position, this->select_start_position_)); + text_render_object->SetCaretPosition(position); + } + } - if (this->enable_) { - if (visible) { - this->SetupCaret(); + void MouseDownHandler(event::MouseButtonEventArgs& args) { + if (this->select_down_button_.has_value()) { + return; } else { - this->TearDownCaret(); + 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::TagDebug(log_tag, "Begin to select text, start position: {}.", + position); } } -} - -template <typename TControl> -void TextControlService<TControl>::SetCaretBlinkDuration(int milliseconds) { - if (this->caret_blink_duration_ == milliseconds) return; - if (this->enable_ && this->caret_visible_) { - this->TearDownCaret(); - this->SetupCaret(); + void 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::TagDebug(log_tag, "End selecting text."); + } } -} -template <typename TControl> -void TextControlService<TControl>::AbortSelection() { - if (this->select_down_button_.has_value()) { - this->control_->ReleaseMouse(); - this->select_down_button_ = std::nullopt; + void LoseFocusHandler(event::FocusChangeEventArgs& args) { + if (!args.IsWindow()) this->AbortSelection(); } - this->control_->GetTextRenderObject()->SetSelectionRange(std::nullopt); -} - -template <typename TControl> -void TextControlService<TControl>::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 <typename TControl> -void TextControlService<TControl>::TearDownCaret() { - const auto application = GetUiApplication(); - application->CancelTimer(this->caret_timer_id_); - this->control_->GetTextRenderObject()->SetDrawCaret(false); -} - -template <typename TControl> -void TextControlService<TControl>::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))}); -} + private: + gsl::not_null<TControl*> control_; + std::vector<EventRevokerGuard> event_revoker_guards_; -template <typename TControl> -void TextControlService<TControl>::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::TagDebug(log_tag, - "Text selection changed on mouse move, range: {}, {}.", - position, this->select_start_position_); - this->control_->GetTextRenderObject()->SetSelectionRange( - TextRange::FromTwoSides( - static_cast<unsigned>(position), - static_cast<unsigned>(this->select_start_position_))); - text_render_object->SetCaretPosition(position); - } -} + bool enable_ = false; -template <typename TControl> -void TextControlService<TControl>::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::TagDebug(log_tag, "Begin to select text, start position: {}.", - position); - } -} + bool caret_visible_ = false; + long long caret_timer_id_ = -1; + int caret_blink_duration_ = k_default_caret_blink_duration; -template <typename TControl> -void TextControlService<TControl>::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::TagDebug(log_tag, "End selecting text."); - } -} + // nullopt means not selecting + std::optional<MouseButton> select_down_button_; -template <typename TControl> -void TextControlService<TControl>::LoseFocusHandler( - event::FocusChangeEventArgs& args) { - if (!args.IsWindow()) this->AbortSelection(); -} + // before the char + int select_start_position_; +}; } // namespace cru::ui::controls |