diff options
-rw-r--r-- | demos/main/main.cpp | 2 | ||||
-rw-r--r-- | include/cru/common/format.hpp | 97 | ||||
-rw-r--r-- | include/cru/common/logger.hpp | 12 | ||||
-rw-r--r-- | include/cru/platform/check.hpp | 6 | ||||
-rw-r--r-- | include/cru/platform/graph/painter.hpp | 4 | ||||
-rw-r--r-- | include/cru/ui/control.hpp | 6 | ||||
-rw-r--r-- | include/cru/ui/controls/text_box.hpp | 6 | ||||
-rw-r--r-- | include/cru/ui/render/scroll_render_object.hpp | 30 | ||||
-rw-r--r-- | include/cru/win/graph/direct/painter.hpp | 8 | ||||
-rw-r--r-- | src/common/CMakeLists.txt | 6 | ||||
-rw-r--r-- | src/common/logger.cpp | 6 | ||||
-rw-r--r-- | src/ui/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/ui/controls/text_box.cpp | 5 | ||||
-rw-r--r-- | src/ui/controls/text_control_service.hpp | 34 | ||||
-rw-r--r-- | src/ui/render/scroll_render_object.cpp | 1 | ||||
-rw-r--r-- | src/win/exception.cpp | 28 | ||||
-rw-r--r-- | src/win/graph/direct/painter.cpp | 17 | ||||
-rw-r--r-- | src/win/native/window.cpp | 1 | ||||
-rw-r--r-- | tools/migrate-files.py | 0 | ||||
-rw-r--r-- | tools/win_build.py | 2 |
20 files changed, 123 insertions, 150 deletions
diff --git a/demos/main/main.cpp b/demos/main/main.cpp index 21db7dda..d39aef88 100644 --- a/demos/main/main.cpp +++ b/demos/main/main.cpp @@ -5,6 +5,7 @@ #include "cru/ui/controls/flex_layout.hpp" #include "cru/ui/controls/stack_layout.hpp" #include "cru/ui/controls/text_block.hpp" +#include "cru/ui/controls/text_box.hpp" #include "cru/ui/ui_host.hpp" #include "cru/ui/window.hpp" @@ -16,6 +17,7 @@ using cru::ui::controls::Button; using cru::ui::controls::FlexLayout; using cru::ui::controls::StackLayout; using cru::ui::controls::TextBlock; +using cru::ui::controls::TextBox; int main() { #ifdef CRU_DEBUG diff --git a/include/cru/common/format.hpp b/include/cru/common/format.hpp deleted file mode 100644 index 4276880e..00000000 --- a/include/cru/common/format.hpp +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once -#include "pre_config.hpp" - -#include <sstream> -#include <string> -#include <string_view> - -namespace cru::util { -namespace details { -template <typename TChar> -struct FormatTrait {}; - -template <> -struct FormatTrait<char> { - using StringType = std::string; - using ViewType = std::string_view; - using StreamType = std::stringstream; - static constexpr ViewType placeholder = "{}"; -}; - -template <> -struct FormatTrait<wchar_t> { - using StringType = std::wstring; - using ViewType = std::wstring_view; - using StreamType = std::wstringstream; - static constexpr ViewType placeholder = L"{}"; -}; -} // namespace details - -template <typename TFormatTrait, typename T> -struct Formatter { - static typename TFormatTrait::StringType Format(const T& value) { - typename TFormatTrait::StreamType stream; - stream << value; - return stream.str(); - } -}; - -namespace details { -template <typename TString> -void FormatInternal(TString& string) { - using Trait = FormatTrait<TString::value_type>; - constexpr const auto& placeholder = Trait::placeholder; - - const auto find_result = string.find(placeholder); - if (find_result != TString::npos) - throw std::invalid_argument("There is more placeholders than args."); -} - -template <typename TString, typename T, typename... TRest> -void FormatInternal(TString& string, const T& arg, const TRest&... args) { - using Trait = FormatTrait<TString::value_type>; - constexpr const auto& placeholder = Trait::placeholder; - - const auto find_result = string.find(placeholder); - if (find_result == TString::npos) - throw std::invalid_argument("There is less placeholders than args."); - - string.replace(find_result, 2, Formatter<Trait, T>::Format(arg)); - FormatInternal<TString>(string, args...); -} -} // namespace details - -template <typename... T> -std::wstring Format(std::wstring format, const T&... args) { - details::FormatInternal(format, args...); - return format; -} - -template <typename... T> -std::string Format(std::string format, const T&... args) { - details::FormatInternal(format, args...); - return format; -} - -// Why is two overloads below exist? -// Because people should be able to pass string_view instance as format. -// However, the two overloads above do not accept string_view as format due to -// conversion from string_view to string is explicit. -// -// Why not just overload but SFINAE? -// Because I want to make two overloads below worse than the two ones above. -// Otherwise it will be ambiguous when pass const char* as format. -// - -template <typename T, typename... TArgs> -auto Format(T format, const TArgs&... args) - -> std::enable_if_t<std::is_same_v<T, std::wstring_view>, std::wstring> { - return Format(std::wstring{format}, args...); -} - -template <typename T, typename... TArgs> -auto Format(T format, const TArgs&... args) - -> std::enable_if_t<std::is_same_v<T, std::string_view>, std::string> { - return Format(std::string{format}, args...); -} -} // namespace cru::util diff --git a/include/cru/common/logger.hpp b/include/cru/common/logger.hpp index 38ddb6eb..ab3f2250 100644 --- a/include/cru/common/logger.hpp +++ b/include/cru/common/logger.hpp @@ -1,8 +1,8 @@ #pragma once - #include "cru/common/base.hpp" -#include "cru/common/format.hpp" +#include <fmt/format.h> +#include <fmt/ostream.h> #include <iostream> #include <list> #include <memory> @@ -68,25 +68,25 @@ template <typename... TArgs> void Debug([[maybe_unused]] TArgs&&... args) { #ifdef CRU_DEBUG Logger::GetInstance()->Log(LogLevel::Debug, - util::Format(std::forward<TArgs>(args)...)); + fmt::format(std::forward<TArgs>(args)...)); #endif } template <typename... TArgs> void Info(TArgs&&... args) { Logger::GetInstance()->Log(LogLevel::Info, - util::Format(std::forward<TArgs>(args)...)); + fmt::format(std::forward<TArgs>(args)...)); } template <typename... TArgs> void Warn(TArgs&&... args) { Logger::GetInstance()->Log(LogLevel::Warn, - util::Format(std::forward<TArgs>(args)...)); + fmt::format(std::forward<TArgs>(args)...)); } template <typename... TArgs> void Error(TArgs&&... args) { Logger::GetInstance()->Log(LogLevel::Error, - util::Format(std::forward<TArgs>(args)...)); + fmt::format(std::forward<TArgs>(args)...)); } } // namespace cru::log diff --git a/include/cru/platform/check.hpp b/include/cru/platform/check.hpp index 6128fe5a..6e353afb 100644 --- a/include/cru/platform/check.hpp +++ b/include/cru/platform/check.hpp @@ -1,8 +1,8 @@ #pragma once -#include "cru/common/format.hpp" #include "exception.hpp" #include "resource.hpp" +#include <fmt/format.h> #include <memory> #include <type_traits> @@ -13,7 +13,7 @@ TTarget* CheckPlatform(INativeResource* resource, Expects(resource); const auto result = dynamic_cast<TTarget*>(resource); if (result == nullptr) { - throw UnsupportPlatformException(util::Format( + throw UnsupportPlatformException(fmt::format( "Try to convert resource to target platform failed. Platform id of " "resource to convert: {} . Target platform id: {} .", resource->GetPlatformId(), target_platform)); @@ -30,7 +30,7 @@ std::shared_ptr<TTarget> CheckPlatform( Expects(resource); const auto result = std::dynamic_pointer_cast<TTarget>(resource); if (result == nullptr) { - throw UnsupportPlatformException(util::Format( + throw UnsupportPlatformException(fmt::format( "Try to convert resource to target platform failed. Platform id of " "resource to convert: {} . Target platform id: {} .", resource->GetPlatformId(), target_platform)); diff --git a/include/cru/platform/graph/painter.hpp b/include/cru/platform/graph/painter.hpp index 1f4ab7cb..b6eb5452 100644 --- a/include/cru/platform/graph/painter.hpp +++ b/include/cru/platform/graph/painter.hpp @@ -20,6 +20,10 @@ struct IPainter : virtual INativeResource { virtual void DrawText(const Point& offset, ITextLayout* text_layout, IBrush* brush) = 0; + virtual void PushLayer(const Rect& bounds) = 0; + + virtual void PopLayer() = 0; + virtual void EndDraw() = 0; }; } // namespace cru::platform::graph diff --git a/include/cru/ui/control.hpp b/include/cru/ui/control.hpp index 30dc589a..d66405e6 100644 --- a/include/cru/ui/control.hpp +++ b/include/cru/ui/control.hpp @@ -147,12 +147,6 @@ class Control : public Object { private: bool is_mouse_over_ = false; - struct { - bool left; - bool middle; - bool right; - } click_map_; - std::shared_ptr<platform::native::ICursor> cursor_ = nullptr; }; } // namespace cru::ui diff --git a/include/cru/ui/controls/text_box.hpp b/include/cru/ui/controls/text_box.hpp index c0160658..15fcb734 100644 --- a/include/cru/ui/controls/text_box.hpp +++ b/include/cru/ui/controls/text_box.hpp @@ -2,6 +2,8 @@ #include "../no_child_control.hpp" #include "base.hpp" +#include <memory> + namespace cru::ui::controls { template <typename TControl> class TextControlService; @@ -10,6 +12,8 @@ class TextBox : public NoChildControl { public: static constexpr std::string_view control_type = "TextBox"; + static TextBox* Create() { return new TextBox(); } + protected: TextBox(); @@ -21,6 +25,8 @@ class TextBox : public NoChildControl { std::string_view GetControlType() const final { return control_type; } + render::RenderObject* GetRenderObject() const override; + render::TextRenderObject* GetTextRenderObject(); const TextBoxBorderStyle& GetBorderStyle(); diff --git a/include/cru/ui/render/scroll_render_object.hpp b/include/cru/ui/render/scroll_render_object.hpp new file mode 100644 index 00000000..1527db6c --- /dev/null +++ b/include/cru/ui/render/scroll_render_object.hpp @@ -0,0 +1,30 @@ +#pragma once +#include "render_object.hpp" + +#include "cru/platform/graph/util/painter.hpp" + +namespace cru::ui::render { +class ScrollRenderObject : public RenderObject { + public: + ScrollRenderObject() : RenderObject(ChildMode::Single) {} + + CRU_DELETE_COPY(ScrollRenderObject) + CRU_DELETE_MOVE(ScrollRenderObject) + + ~ScrollRenderObject() override = default; + + void Draw(platform::graph::IPainter* painter) override; + + RenderObject* HitTest(const Point& point) override; + + Point GetScrollOffset() { return scroll_offset_; } + void SetScrollOffset(const Point& offset); + + protected: + void OnAddChild(RenderObject* new_child, Index position) override; + void OnRemoveChild(RenderObject* removed_child, Index position) override; + + private: + Point scroll_offset_; +}; +} // namespace cru::ui::render diff --git a/include/cru/win/graph/direct/painter.hpp b/include/cru/win/graph/direct/painter.hpp index 5a1fe03f..4f2164c9 100644 --- a/include/cru/win/graph/direct/painter.hpp +++ b/include/cru/win/graph/direct/painter.hpp @@ -4,6 +4,8 @@ #include "cru/platform/graph/painter.hpp" +#include <vector> + namespace cru::platform::graph::win::direct { class D2DPainter : public DirectResource, public virtual IPainter, @@ -35,6 +37,10 @@ class D2DPainter : public DirectResource, void DrawText(const Point& offset, ITextLayout* text_layout, IBrush* brush) override; + void PushLayer(const Rect& bounds) override; + + void PopLayer() override; + void EndDraw() override final; protected: @@ -47,6 +53,8 @@ class D2DPainter : public DirectResource, private: ID2D1RenderTarget* render_target_; + std::vector<Microsoft::WRL::ComPtr<ID2D1Layer>> layers_; + bool is_drawing_ = true; }; } // namespace cru::platform::graph::win::direct diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 6e621bf1..43d1dedb 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -6,7 +6,6 @@ target_sources(cru_base PUBLIC ${CRU_BASE_INCLUDE_DIR}/base.hpp ${CRU_BASE_INCLUDE_DIR}/bitmask.hpp ${CRU_BASE_INCLUDE_DIR}/event.hpp - ${CRU_BASE_INCLUDE_DIR}/format.hpp ${CRU_BASE_INCLUDE_DIR}/logger.hpp ${CRU_BASE_INCLUDE_DIR}/pre_config.hpp ${CRU_BASE_INCLUDE_DIR}/self_resolvable.hpp @@ -14,5 +13,6 @@ target_sources(cru_base PUBLIC target_include_directories(cru_base PUBLIC ${CRU_INCLUDE_DIR}) target_compile_definitions(cru_base PUBLIC $<$<CONFIG:Debug>:CRU_DEBUG>) -find_path(GSL_INCLUDE_DIR NAMES gsl PATH_SUFFIXES ms-gsl) -target_include_directories(cru_base PUBLIC ${GSL_INCLUDE_DIR}) +find_package(Microsoft.GSL CONFIG REQUIRED) +find_package(fmt CONFIG REQUIRED) +target_link_libraries(cru_base PUBLIC Microsoft.GSL::GSL fmt::fmt) diff --git a/src/common/logger.cpp b/src/common/logger.cpp index 6c1422f9..97599b0a 100644 --- a/src/common/logger.cpp +++ b/src/common/logger.cpp @@ -1,7 +1,5 @@ #include "cru/common/logger.hpp" -#include "cru/common/format.hpp" - #include <array> #include <cstdlib> #include <ctime> @@ -60,8 +58,8 @@ void Logger::Log(LogLevel level, const std::string_view &s) { std::array<char, 50> buffer; std::strftime(buffer.data(), 50, "%c", std::localtime(&now)); - source->Write(level, util::Format("[{}] {}: {}\n", buffer.data(), - LogLevelToString(level), s)); + source->Write(level, fmt::format("[{}] {}: {}\n", buffer.data(), + LogLevelToString(level), s)); } } } // namespace cru::log diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index a8fd46a5..777e4fbc 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -25,6 +25,7 @@ add_library(cru_ui STATIC render/flex_layout_render_object.cpp render/layout_utility.cpp render/render_object.cpp + render/scroll_render_object.cpp render/stack_layout_render_object.cpp render/text_render_object.cpp render/window_render_object.cpp @@ -54,6 +55,7 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/render/layout_render_object.hpp ${CRU_UI_INCLUDE_DIR}/render/layout_utility.hpp ${CRU_UI_INCLUDE_DIR}/render/render_object.hpp + ${CRU_UI_INCLUDE_DIR}/render/scroll_render_object.hpp ${CRU_UI_INCLUDE_DIR}/render/stack_layout_render_object.hpp ${CRU_UI_INCLUDE_DIR}/render/text_render_object.hpp ${CRU_UI_INCLUDE_DIR}/render/window_render_object.hpp diff --git a/src/ui/controls/text_box.cpp b/src/ui/controls/text_box.cpp index 7b63eea1..8b7dc692 100644 --- a/src/ui/controls/text_box.cpp +++ b/src/ui/controls/text_box.cpp @@ -29,6 +29,7 @@ TextBox::TextBox() : border_render_object_(new BorderRenderObject()) { service_ = std::make_unique<TextControlService<TextBox>>(this); service_->SetEnabled(true); + service_->SetCaretVisible(true); GainFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) { this->service_->SetEnabled(true); @@ -43,6 +44,10 @@ TextBox::TextBox() : border_render_object_(new BorderRenderObject()) { TextBox::~TextBox() {} +render::RenderObject* TextBox::GetRenderObject() const { + return border_render_object_.get(); +} + render::TextRenderObject* TextBox::GetTextRenderObject() { return text_render_object_.get(); } diff --git a/src/ui/controls/text_control_service.hpp b/src/ui/controls/text_control_service.hpp index 57a6efa7..626423bf 100644 --- a/src/ui/controls/text_control_service.hpp +++ b/src/ui/controls/text_control_service.hpp @@ -10,8 +10,7 @@ #include "cru/ui/ui_event.hpp" namespace cru::ui::controls { -constexpr float caret_width = 2; -constexpr long long caret_blink_duration = 500; +constexpr int k_default_caret_blink_duration = 500; // TControl should inherits `Control` and has following methods: // ``` @@ -31,14 +30,11 @@ class TextControlService : public Object { 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); + int GetCaretBlinkDuration() { return caret_blink_duration_; } + void SetCaretBlinkDuration(int milliseconds); private: void AbortSelection(); @@ -60,8 +56,8 @@ class TextControlService : public Object { bool enable_ = false; bool caret_visible_ = false; - int caret_position_ = 0; long long caret_timer_id_ = -1; + int caret_blink_duration_ = k_default_caret_blink_duration; // nullopt means not selecting std::optional<MouseButton> select_down_button_; @@ -105,12 +101,22 @@ void TextControlService<TControl>::SetCaretVisible(bool visible) { if (this->enable_) { if (visible) { - this->SetupCaretTimer(); + this->SetupCaret(); } else { - this->TearDownCaretTimer(); + this->TearDownCaret(); } } -} // namespace cru::ui::controls +} + +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(); + } +} template <typename TControl> void TextControlService<TControl>::AbortSelection() { @@ -130,7 +136,7 @@ void TextControlService<TControl>::SetupCaret() { this->control_->GetTextRenderObject()->SetDrawCaret(true); this->caret_timer_id_ = application->SetInterval( - std::chrono::milliseconds(caret_blink_duration), + std::chrono::milliseconds(this->caret_blink_duration_), [this] { this->control_->GetTextRenderObject()->ToggleDrawCaret(); }); } @@ -169,7 +175,6 @@ void TextControlService<TControl>::MouseMoveHandler( 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: {}, " "{}.", @@ -178,6 +183,7 @@ void TextControlService<TControl>::MouseMoveHandler( TextRange::FromTwoSides( static_cast<unsigned>(position), static_cast<unsigned>(this->select_start_position_))); + text_render_object->SetCaretPosition(position); } } @@ -194,6 +200,8 @@ void TextControlService<TControl>::MouseDownHandler( 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); diff --git a/src/ui/render/scroll_render_object.cpp b/src/ui/render/scroll_render_object.cpp new file mode 100644 index 00000000..f77ee636 --- /dev/null +++ b/src/ui/render/scroll_render_object.cpp @@ -0,0 +1 @@ +#include "cru/ui/render/scroll_render_object.hpp" diff --git a/src/win/exception.cpp b/src/win/exception.cpp index e0f114b5..fde3ec6f 100644 --- a/src/win/exception.cpp +++ b/src/win/exception.cpp @@ -1,36 +1,32 @@ #include "cru/win/exception.hpp" -#include "cru/common/format.hpp" +#include <fmt/format.h> +#include <optional> namespace cru::platform::win { -using util::Format; inline std::string HResultMakeMessage(HRESULT h_result, - const std::string_view* message) { - char buffer[20]; - sprintf_s(buffer, "%#08x", h_result); - - if (message) - return Format("HRESULT: {}.\nMessage: {}\n", buffer, *message); + std::optional<std::string_view> message) { + if (message.has_value()) + return fmt::format(FMT_STRING("HRESULT: {:#08x}. Message: {}"), h_result, + *message); else - return Format("HRESULT: {}.\n", buffer); + return fmt::format(FMT_STRING("HRESULT: {:#08x}."), h_result); } HResultError::HResultError(HRESULT h_result) - : PlatformException(HResultMakeMessage(h_result, nullptr)), + : PlatformException(HResultMakeMessage(h_result, std::nullopt)), h_result_(h_result) {} HResultError::HResultError(HRESULT h_result, const std::string_view& additional_message) - : PlatformException(HResultMakeMessage(h_result, &additional_message)), + : PlatformException(HResultMakeMessage(h_result, additional_message)), h_result_(h_result) {} inline std::string Win32MakeMessage(DWORD error_code, - const std::string_view& message) { - char buffer[20]; - sprintf_s(buffer, "%#04x", error_code); - - return Format("Last error code: {}.\nMessage: {}\n", buffer, message); + std::string_view message) { + return fmt::format("Last error code: {:#04x}.\nMessage: {}\n", error_code, + message); } Win32Error::Win32Error(const std::string_view& message) diff --git a/src/win/graph/direct/painter.cpp b/src/win/graph/direct/painter.cpp index c5150ad4..df0075e0 100644 --- a/src/win/graph/direct/painter.cpp +++ b/src/win/graph/direct/painter.cpp @@ -71,6 +71,23 @@ void D2DPainter::DrawText(const Point& offset, ITextLayout* text_layout, b->GetD2DBrushInterface()); } +void D2DPainter::PushLayer(const Rect& bounds) { + CheckValidation(); + + Microsoft::WRL::ComPtr<ID2D1Layer> layer; + ThrowIfFailed(render_target_->CreateLayer(&layer)); + + render_target_->PushLayer(D2D1::LayerParameters(Convert(bounds)), + layer.Get()); + + layers_.push_back(std::move(layer)); +} + +void D2DPainter::PopLayer() { + render_target_->PopLayer(); + layers_.pop_back(); +} + void D2DPainter::EndDraw() { if (is_drawing_) { is_drawing_ = false; diff --git a/src/win/native/window.cpp b/src/win/native/window.cpp index bda8e764..bed9a264 100644 --- a/src/win/native/window.cpp +++ b/src/win/native/window.cpp @@ -1,6 +1,5 @@ #include "cru/win/native/window.hpp" -#include "cru/common/format.hpp" #include "cru/common/logger.hpp" #include "cru/platform/check.hpp" #include "cru/win/native/cursor.hpp" diff --git a/tools/migrate-files.py b/tools/migrate-files.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tools/migrate-files.py diff --git a/tools/win_build.py b/tools/win_build.py index e7890d8d..3d6f0591 100644 --- a/tools/win_build.py +++ b/tools/win_build.py @@ -52,7 +52,7 @@ def configure(): 'x64': 'x64' } - subprocess.check_call('vcpkg install gtest:{arch}-windows ms-gsl:{arch}-windows'.format(arch=args.arch), + subprocess.check_call('vcpkg install gtest:{arch}-windows fmt:{arch}-windows ms-gsl:{arch}-windows'.format(arch=args.arch), stdout=sys.stdout, stderr=sys.stderr) # -DCMAKE_EXPORT_COMPILE_COMMANDS=ON |