diff options
author | crupest <crupest@outlook.com> | 2020-04-24 00:03:16 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2020-04-24 00:03:16 +0800 |
commit | 75ff8a6a05afd02aaadf7e3049b0a0e305241182 (patch) | |
tree | 5444bbb3ef80036cc38a827b8ccf03f48b310728 | |
parent | 922d7f6c96f81a33538900f8a8992a5b6f640874 (diff) | |
download | cru-75ff8a6a05afd02aaadf7e3049b0a0e305241182.tar.gz cru-75ff8a6a05afd02aaadf7e3049b0a0e305241182.tar.bz2 cru-75ff8a6a05afd02aaadf7e3049b0a0e305241182.zip |
...
-rw-r--r-- | demos/input_method/main.cpp | 2 | ||||
-rw-r--r-- | include/cru/platform/graph/text_layout.hpp | 2 | ||||
-rw-r--r-- | include/cru/platform/native/ui_application.hpp | 15 | ||||
-rw-r--r-- | include/cru/ui/controls/text_block.hpp | 5 | ||||
-rw-r--r-- | include/cru/ui/controls/text_box.hpp | 5 | ||||
-rw-r--r-- | include/cru/ui/render/text_render_object.hpp | 40 | ||||
-rw-r--r-- | include/cru/win/graph/direct/text_layout.hpp | 2 | ||||
-rw-r--r-- | include/cru/win/native/ui_application.hpp | 10 | ||||
-rw-r--r-- | src/ui/controls/text_block.cpp | 24 | ||||
-rw-r--r-- | src/ui/controls/text_box.cpp | 27 | ||||
-rw-r--r-- | src/ui/controls/text_control_service.hpp | 111 | ||||
-rw-r--r-- | src/ui/render/text_render_object.cpp | 88 | ||||
-rw-r--r-- | src/win/graph/direct/text_layout.cpp | 2 | ||||
-rw-r--r-- | src/win/native/ui_application.cpp | 15 |
14 files changed, 193 insertions, 155 deletions
diff --git a/demos/input_method/main.cpp b/demos/input_method/main.cpp index a3ac1b96..74654a2d 100644 --- a/demos/input_method/main.cpp +++ b/demos/input_method/main.cpp @@ -92,7 +92,7 @@ int main() { gsl::narrow_cast<int>(committed_text.size()); const auto cursor_lefttop = - text_layout->TextSingleRect(cursor_pos, false); + text_layout->TextSinglePoint(cursor_pos, false); painter->FillRectangle(Rect{cursor_lefttop.x, cursor_lefttop.y + anchor_y, 3, font->GetFontSize()}, diff --git a/include/cru/platform/graph/text_layout.hpp b/include/cru/platform/graph/text_layout.hpp index 0d104742..d91834c0 100644 --- a/include/cru/platform/graph/text_layout.hpp +++ b/include/cru/platform/graph/text_layout.hpp @@ -17,7 +17,7 @@ struct ITextLayout : virtual IGraphResource { virtual Rect GetTextBounds() = 0; virtual std::vector<Rect> TextRangeRect(const TextRange& text_range) = 0; - virtual Point TextSingleRect(gsl::index position, bool trailing) = 0; + virtual Point TextSinglePoint(gsl::index position, bool trailing) = 0; virtual TextHitTestResult HitTest(const Point& point) = 0; }; } // namespace cru::platform::graph diff --git a/include/cru/platform/native/ui_application.hpp b/include/cru/platform/native/ui_application.hpp index 006255db..afcc7117 100644 --- a/include/cru/platform/native/ui_application.hpp +++ b/include/cru/platform/native/ui_application.hpp @@ -32,11 +32,16 @@ struct IUiApplication : public virtual INativeResource { virtual void AddOnQuitHandler(std::function<void()> handler) = 0; virtual void InvokeLater(std::function<void()> action) = 0; - virtual unsigned long SetTimeout(std::chrono::milliseconds milliseconds, - std::function<void()> action) = 0; - virtual unsigned long SetInterval(std::chrono::milliseconds milliseconds, - std::function<void()> action) = 0; - virtual void CancelTimer(unsigned long id) = 0; + // Timer id should always be positive and never the same. So it's ok to use + // negative value to represent no timer. + virtual long long SetTimeout(std::chrono::milliseconds milliseconds, + std::function<void()> action) = 0; + virtual long long SetInterval(std::chrono::milliseconds milliseconds, + std::function<void()> action) = 0; + // Implementation should guarantee calls on timer id already canceled have no + // effects and do not crash. Also canceling negative id should always result + // in no-op. + virtual void CancelTimer(long long id) = 0; virtual std::vector<INativeWindow*> GetAllWindow() = 0; virtual std::shared_ptr<INativeWindowResolver> CreateWindow( diff --git a/include/cru/ui/controls/text_block.hpp b/include/cru/ui/controls/text_block.hpp index ec61a08c..61f568c4 100644 --- a/include/cru/ui/controls/text_block.hpp +++ b/include/cru/ui/controls/text_block.hpp @@ -29,14 +29,9 @@ class TextBlock : public NoChildControl { void SetText(std::string text); render::TextRenderObject* GetTextRenderObject(); - render::CanvasRenderObject* GetCaretRenderObject(); - std::shared_ptr<platform::graph::IBrush> GetCaretBrush(); private: - std::unique_ptr<render::StackLayoutRenderObject> root_render_object_; std::unique_ptr<render::TextRenderObject> text_render_object_; - std::unique_ptr<render::CanvasRenderObject> caret_render_object_; - std::shared_ptr<platform::graph::IBrush> caret_brush_; std::unique_ptr<TextControlService<TextBlock>> service_; }; diff --git a/include/cru/ui/controls/text_box.hpp b/include/cru/ui/controls/text_box.hpp index f5347430..c0160658 100644 --- a/include/cru/ui/controls/text_box.hpp +++ b/include/cru/ui/controls/text_box.hpp @@ -22,8 +22,6 @@ class TextBox : public NoChildControl { std::string_view GetControlType() const final { return control_type; } render::TextRenderObject* GetTextRenderObject(); - render::CanvasRenderObject* GetCaretRenderObject(); - std::shared_ptr<platform::graph::IBrush> GetCaretBrush(); const TextBoxBorderStyle& GetBorderStyle(); void SetBorderStyle(TextBoxBorderStyle border_style); @@ -36,11 +34,8 @@ class TextBox : public NoChildControl { private: std::unique_ptr<render::BorderRenderObject> border_render_object_; - std::unique_ptr<render::StackLayoutRenderObject> stack_layout_render_object_; std::unique_ptr<render::TextRenderObject> text_render_object_; - std::unique_ptr<render::CanvasRenderObject> caret_render_object_; - std::shared_ptr<platform::graph::IBrush> caret_brush_; TextBoxBorderStyle border_style_; std::unique_ptr<TextControlService<TextBox>> service_; diff --git a/include/cru/ui/render/text_render_object.hpp b/include/cru/ui/render/text_render_object.hpp index c062c9f0..4b1e91e0 100644 --- a/include/cru/ui/render/text_render_object.hpp +++ b/include/cru/ui/render/text_render_object.hpp @@ -6,9 +6,13 @@ namespace cru::ui::render { class TextRenderObject : public RenderObject { public: + constexpr static float default_caret_width = 2; + + public: TextRenderObject(std::shared_ptr<platform::graph::IBrush> brush, std::shared_ptr<platform::graph::IFont> font, - std::shared_ptr<platform::graph::IBrush> selection_brush); + std::shared_ptr<platform::graph::IBrush> selection_brush, + std::shared_ptr<platform::graph::IBrush> caret_brush); TextRenderObject(const TextRenderObject& other) = delete; TextRenderObject(TextRenderObject&& other) = delete; TextRenderObject& operator=(const TextRenderObject& other) = delete; @@ -19,30 +23,41 @@ class TextRenderObject : public RenderObject { void SetText(std::string new_text); std::shared_ptr<platform::graph::IBrush> GetBrush() const { return brush_; } - void SetBrush(std::shared_ptr<platform::graph::IBrush> new_brush) { - new_brush.swap(brush_); - } + void SetBrush(std::shared_ptr<platform::graph::IBrush> new_brush); std::shared_ptr<platform::graph::IFont> GetFont() const; void SetFont(std::shared_ptr<platform::graph::IFont> font); std::vector<Rect> TextRangeRect(const TextRange& text_range); - Point TextSingleRect(gsl::index position, bool trailing); + Point TextSinglePoint(gsl::index position, bool trailing); platform::graph::TextHitTestResult TextHitTest(const Point& point); std::optional<TextRange> GetSelectionRange() const { return selection_range_; } - void SetSelectionRange(std::optional<TextRange> new_range) { - selection_range_ = std::move(new_range); - } + void SetSelectionRange(std::optional<TextRange> new_range); std::shared_ptr<platform::graph::IBrush> GetSelectionBrush() const { return selection_brush_; } - void SetSelectionBrush(std::shared_ptr<platform::graph::IBrush> new_brush) { - new_brush.swap(selection_brush_); + void SetSelectionBrush(std::shared_ptr<platform::graph::IBrush> new_brush); + + bool IsDrawCaret() const { return draw_caret_; } + void SetDrawCaret(bool draw_caret); + void ToggleDrawCaret() { SetDrawCaret(!IsDrawCaret()); } + + // Caret position can be any value. When it is negative, 0 is used. When it + // exceeds the size of the string, the size of the string is used. + gsl::index GetCaretPosition() const { return caret_position_; } + void SetCaretPosition(gsl::index position); + + std::shared_ptr<platform::graph::IBrush> GetCaretBrush() const { + return caret_brush_; } + void GetCaretBrush(std::shared_ptr<platform::graph::IBrush> brush); + + float GetCaretWidth() const { return caret_width_; } + void SetCaretWidth(float width); void Draw(platform::graph::IPainter* painter) override; @@ -61,5 +76,10 @@ class TextRenderObject : public RenderObject { std::optional<TextRange> selection_range_ = std::nullopt; std::shared_ptr<platform::graph::IBrush> selection_brush_; + + bool draw_caret_ = false; + gsl::index caret_position_ = 0; + std::shared_ptr<platform::graph::IBrush> caret_brush_; + float caret_width_ = default_caret_width; }; } // namespace cru::ui::render diff --git a/include/cru/win/graph/direct/text_layout.hpp b/include/cru/win/graph/direct/text_layout.hpp index 90faadf9..2870db96 100644 --- a/include/cru/win/graph/direct/text_layout.hpp +++ b/include/cru/win/graph/direct/text_layout.hpp @@ -40,7 +40,7 @@ class DWriteTextLayout : public DirectGraphResource, // Return empty vector if text_range.count is 0. Text range could be in // reverse direction, it should be normalized first in implementation. std::vector<Rect> TextRangeRect(const TextRange& text_range) override; - Point TextSingleRect(gsl::index position, bool trailing) override; + Point TextSinglePoint(gsl::index position, bool trailing) override; TextHitTestResult HitTest(const Point& point) override; private: diff --git a/include/cru/win/native/ui_application.hpp b/include/cru/win/native/ui_application.hpp index 1c54cc05..250e855c 100644 --- a/include/cru/win/native/ui_application.hpp +++ b/include/cru/win/native/ui_application.hpp @@ -33,11 +33,11 @@ class WinUiApplication : public WinNativeResource, void AddOnQuitHandler(std::function<void()> handler) override; void InvokeLater(std::function<void()> action) override; - unsigned long SetTimeout(std::chrono::milliseconds milliseconds, - std::function<void()> action) override; - unsigned long SetInterval(std::chrono::milliseconds milliseconds, - std::function<void()> action) override; - void CancelTimer(unsigned long id) override; + long long SetTimeout(std::chrono::milliseconds milliseconds, + std::function<void()> action) override; + long long SetInterval(std::chrono::milliseconds milliseconds, + std::function<void()> action) override; + void CancelTimer(long long id) override; std::vector<INativeWindow*> GetAllWindow() override; std::shared_ptr<INativeWindowResolver> CreateWindow( diff --git a/src/ui/controls/text_block.cpp b/src/ui/controls/text_block.cpp index 1c20540d..a3ec9f54 100644 --- a/src/ui/controls/text_block.cpp +++ b/src/ui/controls/text_block.cpp @@ -11,24 +11,14 @@ using render::CanvasRenderObject; using render::StackLayoutRenderObject; using render::TextRenderObject; -TextBlock::TextBlock() - : root_render_object_(new StackLayoutRenderObject()), - text_render_object_(), - caret_render_object_(new CanvasRenderObject()) { +TextBlock::TextBlock() { const auto theme_resources = UiManager::GetInstance()->GetThemeResources(); text_render_object_ = std::make_unique<TextRenderObject>( theme_resources->text_brush, theme_resources->default_font, - theme_resources->text_selection_brush); + theme_resources->text_selection_brush, theme_resources->caret_brush); - root_render_object_->AddChild(text_render_object_.get(), 0); - root_render_object_->AddChild(caret_render_object_.get(), 1); - - root_render_object_->SetAttachedControl(this); text_render_object_->SetAttachedControl(this); - caret_render_object_->SetAttachedControl(this); - - caret_brush_ = theme_resources->caret_brush; service_ = std::make_unique<TextControlService<TextBlock>>(this); service_->SetEnabled(true); @@ -37,7 +27,7 @@ TextBlock::TextBlock() TextBlock::~TextBlock() = default; render::RenderObject* TextBlock::GetRenderObject() const { - return root_render_object_.get(); + return text_render_object_.get(); } std::string TextBlock::GetText() const { @@ -51,12 +41,4 @@ void TextBlock::SetText(std::string text) { render::TextRenderObject* TextBlock::GetTextRenderObject() { return text_render_object_.get(); } - -render::CanvasRenderObject* TextBlock::GetCaretRenderObject() { - return caret_render_object_.get(); -} - -std::shared_ptr<platform::graph::IBrush> TextBlock::GetCaretBrush() { - return caret_brush_; -} } // namespace cru::ui::controls diff --git a/src/ui/controls/text_box.cpp b/src/ui/controls/text_box.cpp index 99164b9b..7b63eea1 100644 --- a/src/ui/controls/text_box.cpp +++ b/src/ui/controls/text_box.cpp @@ -13,29 +13,22 @@ using render::CanvasRenderObject; using render::StackLayoutRenderObject; using render::TextRenderObject; -TextBox::TextBox() - : border_render_object_(new BorderRenderObject()), - stack_layout_render_object_(new StackLayoutRenderObject()), - text_render_object_(), - caret_render_object_(new CanvasRenderObject()), - service_(new TextControlService<TextBox>(this)) { +TextBox::TextBox() : border_render_object_(new BorderRenderObject()) { const auto theme_resources = UiManager::GetInstance()->GetThemeResources(); - caret_brush_ = theme_resources->caret_brush; border_style_ = theme_resources->text_box_border_style; text_render_object_ = std::make_unique<TextRenderObject>( theme_resources->text_brush, theme_resources->default_font, - theme_resources->text_selection_brush); + theme_resources->text_selection_brush, theme_resources->caret_brush); - border_render_object_->AddChild(stack_layout_render_object_.get(), 0); - stack_layout_render_object_->AddChild(text_render_object_.get(), 0); - stack_layout_render_object_->AddChild(caret_render_object_.get(), 1); + border_render_object_->AddChild(text_render_object_.get(), 0); border_render_object_->SetAttachedControl(this); - stack_layout_render_object_->SetAttachedControl(this); text_render_object_->SetAttachedControl(this); - caret_render_object_->SetAttachedControl(this); + + service_ = std::make_unique<TextControlService<TextBox>>(this); + service_->SetEnabled(true); GainFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) { this->service_->SetEnabled(true); @@ -54,14 +47,6 @@ render::TextRenderObject* TextBox::GetTextRenderObject() { return text_render_object_.get(); } -render::CanvasRenderObject* TextBox::GetCaretRenderObject() { - return caret_render_object_.get(); -} - -std::shared_ptr<platform::graph::IBrush> TextBox::GetCaretBrush() { - return caret_brush_; -} - const TextBoxBorderStyle& TextBox::GetBorderStyle() { return border_style_; } void TextBox::SetBorderStyle(TextBoxBorderStyle border_style) { diff --git a/src/ui/controls/text_control_service.hpp b/src/ui/controls/text_control_service.hpp index 0da8abed..57a6efa7 100644 --- a/src/ui/controls/text_control_service.hpp +++ b/src/ui/controls/text_control_service.hpp @@ -1,4 +1,5 @@ #pragma once +#include "../helper.hpp" #include "cru/common/logger.hpp" #include "cru/platform/graph/font.hpp" #include "cru/platform/graph/painter.hpp" @@ -15,8 +16,6 @@ constexpr long long caret_blink_duration = 500; // TControl should inherits `Control` and has following methods: // ``` // render::TextRenderObject* GetTextRenderObject(); -// render::CanvasRenderObject* GetCaretRenderObject(); -// std::shared_ptr<platform::graph::IBrush> GetCaretBrush(); // ``` template <typename TControl> class TextControlService : public Object { @@ -44,8 +43,8 @@ class TextControlService : public Object { private: void AbortSelection(); - void SetupCaretTimer(); - void TearDownCaretTimer(); + void SetupCaret(); + void TearDownCaret(); void SetupHandlers(); @@ -62,12 +61,7 @@ class TextControlService : public Object { 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; + long long caret_timer_id_ = -1; // nullopt means not selecting std::optional<MouseButton> select_down_button_; @@ -82,101 +76,86 @@ TextControlService<TControl>::TextControlService(TControl* control) template <typename TControl> TextControlService<TControl>::~TextControlService() { - if (enable_ && caret_visible_) TearDownCaretTimer(); + 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 <typename TControl> void TextControlService<TControl>::SetEnabled(bool enable) { - if (enable == enable_) return; + if (enable == this->enable_) return; if (enable) { - AbortSelection(); - SetupHandlers(); - if (caret_visible_) { - SetupCaretTimer(); + this->SetupHandlers(); + if (this->caret_visible_) { + this->SetupCaret(); } } else { - event_revoker_guards_.clear(); - if (caret_visible_) { - TearDownCaretTimer(); - } + this->AbortSelection(); + this->event_revoker_guards_.clear(); + this->TearDownCaret(); } } template <typename TControl> void TextControlService<TControl>::SetCaretVisible(bool visible) { - if (visible == caret_visible_) return; + if (visible == this->caret_visible_) return; + + this->caret_visible_ = visible; - if (enable_) { + if (this->enable_) { if (visible) { - SetupCaretTimer(); + this->SetupCaretTimer(); + } else { + this->TearDownCaretTimer(); } - } else { - TearDownCaretTimer(); } } // namespace cru::ui::controls template <typename TControl> -void TextControlService<TControl>::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().get()); - } -} - -template <typename TControl> void TextControlService<TControl>::AbortSelection() { - if (select_down_button_.has_value()) { - control_->ReleaseMouse(); - select_down_button_ = std::nullopt; + if (this->select_down_button_.has_value()) { + this->control_->ReleaseMouse(); + this->select_down_button_ = std::nullopt; } - control_->GetTextRenderObject()->SetSelectionRange(std::nullopt); + this->control_->GetTextRenderObject()->SetSelectionRange(std::nullopt); } template <typename TControl> -void TextControlService<TControl>::SetupCaretTimer() { -#ifdef CRU_DEBUG - Expects(!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(); - }); +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(caret_blink_duration), + [this] { this->control_->GetTextRenderObject()->ToggleDrawCaret(); }); } template <typename TControl> -void TextControlService<TControl>::TearDownCaretTimer() { -#ifdef CRU_DEBUG - Expects(!caret_timer_set_); - caret_timer_set_ = false; -#endif - platform::native::IUiApplication::GetInstance()->CancelTimer( - caret_timer_tag_); +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()); - event_revoker_guards_.push_back( + this->event_revoker_guards_.push_back( EventRevokerGuard{control_->MouseMoveEvent()->Direct()->AddHandler( std::bind(&TextControlService::MouseMoveHandler, this, std::placeholders::_1))}); - event_revoker_guards_.push_back( + this->event_revoker_guards_.push_back( EventRevokerGuard{control_->MouseDownEvent()->Direct()->AddHandler( std::bind(&TextControlService::MouseDownHandler, this, std::placeholders::_1))}); - event_revoker_guards_.push_back(EventRevokerGuard{ + this->event_revoker_guards_.push_back(EventRevokerGuard{ control_->MouseUpEvent()->Direct()->AddHandler(std::bind( &TextControlService::MouseUpHandler, this, std::placeholders::_1))}); - event_revoker_guards_.push_back( + this->event_revoker_guards_.push_back( EventRevokerGuard{control_->LoseFocusEvent()->Direct()->AddHandler( std::bind(&TextControlService::LoseFocusHandler, this, std::placeholders::_1))}); @@ -199,8 +178,6 @@ void TextControlService<TControl>::MouseMoveHandler( TextRange::FromTwoSides( static_cast<unsigned>(position), static_cast<unsigned>(this->select_start_position_))); - this->control_->GetTextRenderObject()->InvalidatePaint(); - this->control_->GetCaretRenderObject()->InvalidatePaint(); } } diff --git a/src/ui/render/text_render_object.cpp b/src/ui/render/text_render_object.cpp index e8368480..cd670db1 100644 --- a/src/ui/render/text_render_object.cpp +++ b/src/ui/render/text_render_object.cpp @@ -7,22 +7,23 @@ #include <algorithm> -// TODO: Null Check!!! - namespace cru::ui::render { TextRenderObject::TextRenderObject( std::shared_ptr<platform::graph::IBrush> brush, std::shared_ptr<platform::graph::IFont> font, - std::shared_ptr<platform::graph::IBrush> selection_brush) { + std::shared_ptr<platform::graph::IBrush> selection_brush, + std::shared_ptr<platform::graph::IBrush> caret_brush) { Expects(brush); Expects(font); Expects(selection_brush); + Expects(caret_brush); SetChildMode(ChildMode::None); brush.swap(brush_); font.swap(font_); selection_brush.swap(selection_brush_); + caret_brush.swap(caret_brush_); const auto graph_factory = GetGraphFactory(); text_layout_ = graph_factory->CreateTextLayout(font_, ""); @@ -38,11 +39,19 @@ void TextRenderObject::SetText(std::string new_text) { text_layout_->SetText(std::move(new_text)); } +void TextRenderObject::SetBrush( + std::shared_ptr<platform::graph::IBrush> new_brush) { + Expects(new_brush); + new_brush.swap(brush_); + InvalidatePaint(); +} + std::shared_ptr<platform::graph::IFont> TextRenderObject::GetFont() const { return text_layout_->GetFont(); } void TextRenderObject::SetFont(std::shared_ptr<platform::graph::IFont> font) { + Expects(font); text_layout_->SetFont(std::move(font)); } @@ -50,8 +59,8 @@ std::vector<Rect> TextRenderObject::TextRangeRect(const TextRange& text_range) { return text_layout_->TextRangeRect(text_range); } -Point TextRenderObject::TextSingleRect(gsl::index position, bool trailing) { - return text_layout_->TextSingleRect(position, trailing); +Point TextRenderObject::TextSinglePoint(gsl::index position, bool trailing) { + return text_layout_->TextSinglePoint(position, trailing); } platform::graph::TextHitTestResult TextRenderObject::TextHitTest( @@ -59,6 +68,54 @@ platform::graph::TextHitTestResult TextRenderObject::TextHitTest( return text_layout_->HitTest(point); } +void TextRenderObject::SetSelectionRange(std::optional<TextRange> new_range) { + selection_range_ = std::move(new_range); + InvalidatePaint(); +} + +void TextRenderObject::SetSelectionBrush( + std::shared_ptr<platform::graph::IBrush> new_brush) { + Expects(new_brush); + new_brush.swap(selection_brush_); + if (selection_range_ && selection_range_->count) { + InvalidatePaint(); + } +} + +void TextRenderObject::SetDrawCaret(bool draw_caret) { + if (draw_caret_ != draw_caret) { + draw_caret_ = draw_caret; + InvalidatePaint(); + } +} + +void TextRenderObject::SetCaretPosition(gsl::index position) { + if (position != caret_position_) { + caret_position_ = position; + if (draw_caret_) { + InvalidatePaint(); + } + } +} + +void TextRenderObject::GetCaretBrush( + std::shared_ptr<platform::graph::IBrush> brush) { + Expects(brush); + brush.swap(caret_brush_); + if (draw_caret_) { + InvalidatePaint(); + } +} + +void TextRenderObject::SetCaretWidth(const float width) { + Expects(width >= 0.0f); + + caret_width_ = width; + if (draw_caret_) { + InvalidatePaint(); + } +} + void TextRenderObject::Draw(platform::graph::IPainter* painter) { platform::graph::util::WithTransform( painter, @@ -71,7 +128,28 @@ void TextRenderObject::Draw(platform::graph::IPainter* painter) { for (const auto& rect : rects) p->FillRectangle(rect, this->GetSelectionBrush().get()); } + p->DrawText(Point{}, text_layout_.get(), brush_.get()); + + if (this->draw_caret_ && this->caret_width_ != 0.0f) { + auto caret_pos = this->caret_position_; + gsl::index text_size = this->GetText().size(); + if (caret_pos < 0) { + caret_pos = 0; + } else if (caret_pos > text_size) { + caret_pos = text_size; + } + + const auto caret_top_center = + this->text_layout_->TextSinglePoint(caret_pos, false); + + const auto font_height = this->font_->GetFontSize(); + const auto caret_width = this->caret_width_; + + p->FillRectangle(Rect{caret_top_center.x - caret_width / 2.0f, + caret_top_center.y, caret_width, font_height}, + this->caret_brush_.get()); + } }); } diff --git a/src/win/graph/direct/text_layout.cpp b/src/win/graph/direct/text_layout.cpp index 4a742694..7b8d2ab0 100644 --- a/src/win/graph/direct/text_layout.cpp +++ b/src/win/graph/direct/text_layout.cpp @@ -105,7 +105,7 @@ std::vector<Rect> DWriteTextLayout::TextRangeRect( return result; } -Point DWriteTextLayout::TextSingleRect(gsl::index position, bool trailing) { +Point DWriteTextLayout::TextSinglePoint(gsl::index position, bool trailing) { const auto index = IndexUtf8ToUtf16(text_, static_cast<int>(position), w_text_); diff --git a/src/win/native/ui_application.cpp b/src/win/native/ui_application.cpp index 9aa3ebcd..599ecadc 100644 --- a/src/win/native/ui_application.cpp +++ b/src/win/native/ui_application.cpp @@ -73,19 +73,20 @@ void WinUiApplication::InvokeLater(std::function<void()> action) { throw Win32Error(::GetLastError(), "InvokeLater failed to post message."); } -unsigned long WinUiApplication::SetTimeout( - std::chrono::milliseconds milliseconds, std::function<void()> action) { - return static_cast<unsigned long>(timer_manager_->CreateTimer( +long long WinUiApplication::SetTimeout(std::chrono::milliseconds milliseconds, + std::function<void()> action) { + return gsl::narrow<long long>(timer_manager_->CreateTimer( static_cast<UINT>(milliseconds.count()), false, std::move(action))); } -unsigned long WinUiApplication::SetInterval( - std::chrono::milliseconds milliseconds, std::function<void()> action) { - return static_cast<unsigned long>(timer_manager_->CreateTimer( +long long WinUiApplication::SetInterval(std::chrono::milliseconds milliseconds, + std::function<void()> action) { + return gsl::narrow<long long>(timer_manager_->CreateTimer( static_cast<UINT>(milliseconds.count()), true, std::move(action))); } -void WinUiApplication::CancelTimer(unsigned long id) { +void WinUiApplication::CancelTimer(long long id) { + if (id < 0) return; timer_manager_->KillTimer(static_cast<UINT_PTR>(id)); } |