From 46ff47d2f47a66372ca0a8a09dd08c4fb04004f3 Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 17 Oct 2020 15:57:53 +0800 Subject: Refactor timer. --- include/cru/common/Event.hpp | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) (limited to 'include/cru/common') diff --git a/include/cru/common/Event.hpp b/include/cru/common/Event.hpp index 377ca7f3..6417bc78 100644 --- a/include/cru/common/Event.hpp +++ b/include/cru/common/Event.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -183,6 +184,7 @@ struct EventRevokerDestroyer { }; } // namespace details +// A guard class for event revoker. Automatically revoke it when destroyed. class EventRevokerGuard { public: EventRevokerGuard() = default; @@ -201,7 +203,7 @@ class EventRevokerGuard { return *revoker_; } - void Release() { revoker_.release(); } + EventRevoker Release() { return std::move(*revoker_.release()); } void Reset(EventRevoker&& revoker) { revoker_.reset(new EventRevoker(std::move(revoker))); @@ -209,5 +211,28 @@ class EventRevokerGuard { private: std::unique_ptr revoker_; -}; // namespace cru +}; + +class EventRevokerListGuard { + public: + EventRevokerListGuard() = default; + EventRevokerListGuard(const EventRevokerListGuard& other) = delete; + EventRevokerListGuard(EventRevokerListGuard&& other) = default; + EventRevokerListGuard& operator=(const EventRevokerListGuard& other) = delete; + EventRevokerListGuard& operator=(EventRevokerListGuard&& other) = default; + ~EventRevokerListGuard() = default; + + public: + void Add(EventRevoker&& revoker) { + event_revoker_guard_list_.push_back(EventRevokerGuard(std::move(revoker))); + } + + EventRevokerListGuard& operator+=(EventRevoker&& revoker) { + this->Add(std::move(revoker)); + return *this; + } + + private: + std::vector event_revoker_guard_list_; +}; } // namespace cru -- cgit v1.2.3 From 5729a5aa1b443e3e25f3e14dee29636d3b31a6f8 Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 18 Oct 2020 21:09:21 +0800 Subject: ... --- demos/main/main.cpp | 34 ++++++++-------------- include/cru/common/Format.hpp | 23 +++++++++++++++ include/cru/common/Logger.hpp | 2 ++ include/cru/platform/GraphBase.hpp | 9 ++++++ include/cru/ui/Base.hpp | 3 ++ include/cru/ui/UiManager.hpp | 4 +++ include/cru/ui/render/FlexLayoutRenderObject.hpp | 4 +++ include/cru/ui/render/MeasureRequirement.hpp | 28 +++++++++++++++--- include/cru/ui/render/RenderObject.hpp | 7 +++++ include/cru/ui/render/WindowRenderObject.hpp | 5 ++++ src/common/CMakeLists.txt | 1 + src/common/Logger.cpp | 1 - src/ui/UiManager.cpp | 5 +++- src/ui/render/FlexLayoutRenderObject.cpp | 6 +++- src/ui/render/RenderObject.cpp | 36 ++++++++++++++++++++++++ src/ui/render/WindowRenderObject.cpp | 4 +++ src/win/CMakeLists.txt | 1 + src/win/StdOutLogger.hpp | 24 ++++++++++++++++ src/win/native/UiApplication.cpp | 3 ++ 19 files changed, 170 insertions(+), 30 deletions(-) create mode 100644 include/cru/common/Format.hpp create mode 100644 src/win/StdOutLogger.hpp (limited to 'include/cru/common') diff --git a/demos/main/main.cpp b/demos/main/main.cpp index 82038731..3026baeb 100644 --- a/demos/main/main.cpp +++ b/demos/main/main.cpp @@ -1,6 +1,7 @@ #include "cru/platform/HeapDebug.hpp" #include "cru/platform/native/UiApplication.hpp" #include "cru/platform/native/Window.hpp" +#include "cru/ui/Base.hpp" #include "cru/ui/UiHost.hpp" #include "cru/ui/Window.hpp" #include "cru/ui/controls/Button.hpp" @@ -10,12 +11,9 @@ #include "cru/ui/controls/TextBox.hpp" using cru::platform::native::CreateUiApplication; -using cru::ui::Rect; -using cru::ui::Thickness; using cru::ui::Window; 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; @@ -31,30 +29,20 @@ int main() { const auto flex_layout = FlexLayout::Create(); window->SetChild(flex_layout); + flex_layout->SetFlexDirection(cru::ui::FlexDirection::Vertical); - const auto button = Button::Create(); - const auto text_block1 = TextBlock::Create(); - text_block1->SetText(u"Hello World!"); - button->SetChild(text_block1); - flex_layout->AddChild(button, 0); - - const auto text_block2 = TextBlock::Create(); - text_block2->SetText(u"Hello World!"); - - const auto text_block3 = TextBlock::Create(); - text_block3->SetText(u"Overlapped text"); + const auto text_block = TextBlock::Create(); + text_block->SetText(u"Hello World from CruUI!"); + flex_layout->AddChild(text_block, 0); - const auto stack_layout = StackLayout::Create(); - stack_layout->AddChild(text_block2, 0); - stack_layout->AddChild(text_block3, 1); - flex_layout->AddChild(stack_layout, 1); - - const auto text_block4 = TextBlock::Create(); - text_block4->SetText(u"Hello World!!!"); - flex_layout->AddChild(text_block4, 2); + const auto button_text_block = TextBlock::Create(); + button_text_block->SetText(u"OK"); + const auto button = Button::Create(); + button->SetChild(button_text_block); + flex_layout->AddChild(button, 1); const auto text_box = TextBox::Create(); - flex_layout->AddChild(text_box, 3); + flex_layout->AddChild(text_box, 2); window->GetUiHost()->GetNativeWindowResolver()->Resolve()->SetVisible(true); diff --git a/include/cru/common/Format.hpp b/include/cru/common/Format.hpp new file mode 100644 index 00000000..59f34036 --- /dev/null +++ b/include/cru/common/Format.hpp @@ -0,0 +1,23 @@ +#pragma once +#include "Base.hpp" + +#include "StringUtil.hpp" + +#include +#include +#include +#include +#include +#include + +namespace cru { +template >> +std::u16string ToUtf16String(T number) { + std::array buffer; + auto result = + std::to_chars(buffer.data(), buffer.data() + buffer.size(), number); + Ensures(result.ec == std::errc()); + std::string_view utf8_result(buffer.data(), result.ptr - buffer.data()); + return ToUtf16(utf8_result); +} +} // namespace cru diff --git a/include/cru/common/Logger.hpp b/include/cru/common/Logger.hpp index 4ea17c09..daf2e7d2 100644 --- a/include/cru/common/Logger.hpp +++ b/include/cru/common/Logger.hpp @@ -40,6 +40,7 @@ class Logger : public Object { std::list> sources_; }; +// TODO: Remove argument evaluation in Debug. template void Debug([[maybe_unused]] TArgs&&... args) { #ifdef CRU_DEBUG @@ -66,6 +67,7 @@ void Error(TArgs&&... args) { fmt::format(std::forward(args)...)); } +// TODO: Remove argument evaluation in Debug. template void TagDebug([[maybe_unused]] std::u16string_view tag, [[maybe_unused]] TArgs&&... args) { diff --git a/include/cru/platform/GraphBase.hpp b/include/cru/platform/GraphBase.hpp index 186ee9d0..571b36f7 100644 --- a/include/cru/platform/GraphBase.hpp +++ b/include/cru/platform/GraphBase.hpp @@ -1,9 +1,13 @@ #pragma once #include "cru/common/Base.hpp" +#include "cru/common/Format.hpp" + +#include #include #include #include +#include #include namespace cru::platform { @@ -46,6 +50,11 @@ struct Size final { std::numeric_limits::max()}; } + std::u16string ToDebugString() const { + return fmt::format(u"({}, {})", ToUtf16String(width), + ToUtf16String(height)); + } + float width = 0; float height = 0; }; diff --git a/include/cru/ui/Base.hpp b/include/cru/ui/Base.hpp index 6be359ab..0c0a4783 100644 --- a/include/cru/ui/Base.hpp +++ b/include/cru/ui/Base.hpp @@ -8,6 +8,9 @@ #include #include +// Change 0 to 1 to enable debug layout log. +#define CRUUI_DEBUG_LAYOUT 0 + namespace cru::ui { //-------------------- region: import -------------------- using cru::platform::Color; diff --git a/include/cru/ui/UiManager.hpp b/include/cru/ui/UiManager.hpp index e6facdbd..46f06ac2 100644 --- a/include/cru/ui/UiManager.hpp +++ b/include/cru/ui/UiManager.hpp @@ -3,8 +3,12 @@ #include "controls/Base.hpp" +#include +#include + namespace cru::ui { struct ThemeResources { + std::u16string default_font_family; std::shared_ptr default_font; std::shared_ptr text_brush; std::shared_ptr text_selection_brush; diff --git a/include/cru/ui/render/FlexLayoutRenderObject.hpp b/include/cru/ui/render/FlexLayoutRenderObject.hpp index ee29d1e4..a8154487 100644 --- a/include/cru/ui/render/FlexLayoutRenderObject.hpp +++ b/include/cru/ui/render/FlexLayoutRenderObject.hpp @@ -1,6 +1,8 @@ #pragma once #include "LayoutRenderObject.hpp" +#include + namespace cru::ui::render { // Measure Logic (v0.1): // Cross axis measure logic is the same as stack layout. @@ -85,6 +87,8 @@ class FlexLayoutRenderObject : public LayoutRenderObject { FlexLayoutRenderObject& operator=(FlexLayoutRenderObject&& other) = delete; ~FlexLayoutRenderObject() override = default; + std::u16string_view GetName() const override; + FlexDirection GetFlexDirection() const { return direction_; } void SetFlexDirection(FlexDirection direction) { direction_ = direction; diff --git a/include/cru/ui/render/MeasureRequirement.hpp b/include/cru/ui/render/MeasureRequirement.hpp index 2be159f8..6a0c6952 100644 --- a/include/cru/ui/render/MeasureRequirement.hpp +++ b/include/cru/ui/render/MeasureRequirement.hpp @@ -1,8 +1,12 @@ #pragma once #include "Base.hpp" +#include "cru/common/Format.hpp" + +#include #include #include +#include namespace cru::ui::render { constexpr Size Min(const Size& left, const Size& right) { @@ -112,6 +116,11 @@ class MeasureLength final { } } + std::u16string ToDebugString() const { + return IsSpecified() ? ToUtf16String(GetLengthOrUndefined()) + : u"UNSPECIFIED"; + } + private: // -1 for not specify float length_; @@ -160,6 +169,11 @@ struct MeasureSize { }; } + std::u16string ToDebugString() const { + return fmt::format(u"({}, {})", width.ToDebugString(), + height.ToDebugString()); + } + constexpr static MeasureSize NotSpecified() { return MeasureSize{MeasureLength::NotSpecified(), MeasureLength::NotSpecified()}; @@ -187,10 +201,11 @@ struct MeasureRequirement { : max(max), min(min) {} constexpr bool Satisfy(const Size& size) const { - return max.width.GetLengthOrMax() >= size.width && - max.height.GetLengthOrMax() >= size.height && - min.width.GetLengthOr0() <= size.width && - min.height.GetLengthOr0() <= size.height; + auto normalized = Normalize(); + return normalized.max.width.GetLengthOrMax() >= size.width && + normalized.max.height.GetLengthOrMax() >= size.height && + normalized.min.width.GetLengthOr0() <= size.width && + normalized.min.height.GetLengthOr0() <= size.height; } constexpr MeasureRequirement Normalize() const { @@ -225,6 +240,11 @@ struct MeasureRequirement { return result; } + std::u16string ToDebugString() const { + return fmt::format(u"{{min: {}, max: {}}}", min.ToDebugString(), + max.ToDebugString()); + } + constexpr static MeasureRequirement Merge(const MeasureRequirement& left, const MeasureRequirement& right) { return MeasureRequirement{MeasureSize::Min(left.max, right.max), diff --git a/include/cru/ui/render/RenderObject.hpp b/include/cru/ui/render/RenderObject.hpp index f820f029..57251e3a 100644 --- a/include/cru/ui/render/RenderObject.hpp +++ b/include/cru/ui/render/RenderObject.hpp @@ -4,6 +4,9 @@ #include "MeasureRequirement.hpp" #include "cru/common/Event.hpp" +#include +#include + namespace cru::ui::render { // Render object will not destroy its children when destroyed. Control must @@ -133,6 +136,10 @@ class RenderObject : public Object { void InvalidateLayout(); void InvalidatePaint(); + public: + virtual std::u16string_view GetName() const; + std::u16string GetDebugPathInTree() const; + protected: void SetChildMode(ChildMode mode) { child_mode_ = mode; } diff --git a/include/cru/ui/render/WindowRenderObject.hpp b/include/cru/ui/render/WindowRenderObject.hpp index 4c254f42..d2ca5526 100644 --- a/include/cru/ui/render/WindowRenderObject.hpp +++ b/include/cru/ui/render/WindowRenderObject.hpp @@ -1,6 +1,8 @@ #pragma once #include "RenderObject.hpp" +#include + namespace cru::ui::render { class WindowRenderObject : public RenderObject { public: @@ -13,6 +15,9 @@ class WindowRenderObject : public RenderObject { RenderObject* HitTest(const Point& point) override; + public: + std::u16string_view GetName() const override; + protected: Size OnMeasureContent(const MeasureRequirement& requirement, const MeasureSize& preferred_size) override; diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 6a18ef2b..73ad9456 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -7,6 +7,7 @@ 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}/PreConfig.hpp ${CRU_BASE_INCLUDE_DIR}/SelfResolvable.hpp diff --git a/src/common/Logger.cpp b/src/common/Logger.cpp index dfa25347..af1dd692 100644 --- a/src/common/Logger.cpp +++ b/src/common/Logger.cpp @@ -73,7 +73,6 @@ void Logger::Log(LogLevel level, std::u16string_view tag, } #endif for (const auto &source : sources_) { - auto now = std::time(nullptr); source->Write(level, fmt::format(u"[{}] {} {}: {}\n", GetLogTime(), LogLevelToString(level), tag, s)); } diff --git a/src/ui/UiManager.cpp b/src/ui/UiManager.cpp index 4cd38efa..8a2029b9 100644 --- a/src/ui/UiManager.cpp +++ b/src/ui/UiManager.cpp @@ -30,7 +30,10 @@ UiManager* UiManager::GetInstance() { UiManager::UiManager() { const auto factory = GetGraphFactory(); - theme_resource_.default_font = factory->CreateFont(u"等线", 24.0f); + theme_resource_.default_font_family = u"等线"; + + theme_resource_.default_font = + factory->CreateFont(theme_resource_.default_font_family, 24.0f); const auto black_brush = std::shared_ptr( CreateSolidColorBrush(factory, colors::black)); diff --git a/src/ui/render/FlexLayoutRenderObject.cpp b/src/ui/render/FlexLayoutRenderObject.cpp index ade230b5..1c39cc8f 100644 --- a/src/ui/render/FlexLayoutRenderObject.cpp +++ b/src/ui/render/FlexLayoutRenderObject.cpp @@ -10,6 +10,10 @@ namespace cru::ui::render { +std::u16string_view FlexLayoutRenderObject::GetName() const { + return u"FlexLayoutRenderObject"; +} + struct tag_horizontal_t {}; struct tag_vertical_t {}; @@ -64,7 +68,7 @@ template constexpr TSize CreateTSize(decltype(std::declval().width) main, decltype(std::declval().height) cross, tag_vertical_t) { - return TSize{main, cross}; + return TSize{cross, main}; } enum class FlexLayoutAdjustType { None, Expand, Shrink }; diff --git a/src/ui/render/RenderObject.cpp b/src/ui/render/RenderObject.cpp index 30433868..bc2228d3 100644 --- a/src/ui/render/RenderObject.cpp +++ b/src/ui/render/RenderObject.cpp @@ -5,6 +5,9 @@ #include "cru/ui/UiHost.hpp" #include +#include +#include +#include namespace cru::ui::render { void RenderObject::AddChild(RenderObject* render_object, const Index position) { @@ -66,7 +69,19 @@ void RenderObject::Measure(const MeasureRequirement& requirement, MeasureSize merged_preferred_size = preferred_size.OverrideBy(preferred_size_); +#if CRUUI_DEBUG_LAYOUT + log::Debug(u"{} Measure begins :\nrequirement: {}\npreferred size: {}", + this->GetDebugPathInTree(), requirement.ToDebugString(), + preferred_size.ToDebugString()); +#endif + size_ = OnMeasureCore(merged_requirement, merged_preferred_size); + +#if CRUUI_DEBUG_LAYOUT + log::Debug(u"{} Measure ends :\nresult size: {}", this->GetDebugPathInTree(), + size_.ToDebugString()); +#endif + Ensures(size_.width >= 0); Ensures(size_.height >= 0); Ensures(requirement.Satisfy(size_)); @@ -256,6 +271,27 @@ void RenderObject::InvalidatePaint() { if (ui_host_ != nullptr) ui_host_->InvalidatePaint(); } +constexpr std::u16string_view kUnamedName(u"UNNAMED"); + +std::u16string_view RenderObject::GetName() const { return kUnamedName; } + +std::u16string RenderObject::GetDebugPathInTree() const { + std::vector chain; + const RenderObject* parent = this; + while (parent != nullptr) { + chain.push_back(parent->GetName()); + parent = parent->GetParent(); + } + + std::u16string result(chain.back()); + for (auto iter = chain.crbegin() + 1; iter != chain.crend(); ++iter) { + result += u" -> "; + result += *iter; + } + + return result; +} + void RenderObject::NotifyAfterLayoutRecursive(RenderObject* render_object) { render_object->OnAfterLayout(); for (const auto o : render_object->GetChildren()) { diff --git a/src/ui/render/WindowRenderObject.cpp b/src/ui/render/WindowRenderObject.cpp index 4adf559e..a136c1e9 100644 --- a/src/ui/render/WindowRenderObject.cpp +++ b/src/ui/render/WindowRenderObject.cpp @@ -24,6 +24,10 @@ RenderObject* WindowRenderObject::HitTest(const Point& point) { return Rect{Point{}, GetSize()}.IsPointInside(point) ? this : nullptr; } +std::u16string_view WindowRenderObject::GetName() const { + return u"WindowRenderObject"; +} + Size WindowRenderObject::OnMeasureContent(const MeasureRequirement& requirement, const MeasureSize& preferred_size) { if (const auto child = GetChild()) { diff --git a/src/win/CMakeLists.txt b/src/win/CMakeLists.txt index 75b0a7ca..06f947a1 100644 --- a/src/win/CMakeLists.txt +++ b/src/win/CMakeLists.txt @@ -2,6 +2,7 @@ set(CRU_WIN_BASE_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/win/) add_library(cru_win_base STATIC DebugLogger.hpp + StdOutLogger.hpp Exception.cpp HeapDebug.cpp diff --git a/src/win/StdOutLogger.hpp b/src/win/StdOutLogger.hpp new file mode 100644 index 00000000..bff8b30e --- /dev/null +++ b/src/win/StdOutLogger.hpp @@ -0,0 +1,24 @@ +#include "cru/common/Base.hpp" +#include "cru/win/WinPreConfig.hpp" + +#include "cru/common/Logger.hpp" + +#include + +namespace cru::platform::win { +class WinStdOutLoggerSource : public ::cru::log::ILogSource { + public: + WinStdOutLoggerSource() = default; + + CRU_DELETE_COPY(WinStdOutLoggerSource) + CRU_DELETE_MOVE(WinStdOutLoggerSource) + + ~WinStdOutLoggerSource() = default; + + void Write(::cru::log::LogLevel level, const std::u16string& s) override { + CRU_UNUSED(level) + + std::fputws(reinterpret_cast(s.c_str()), stdout); + } +}; +} // namespace cru::platform::win diff --git a/src/win/native/UiApplication.cpp b/src/win/native/UiApplication.cpp index a806db88..198f03f2 100644 --- a/src/win/native/UiApplication.cpp +++ b/src/win/native/UiApplication.cpp @@ -1,6 +1,7 @@ #include "cru/win/native/UiApplication.hpp" #include "../DebugLogger.hpp" +#include "../StdOutLogger.hpp" #include "TimerManager.hpp" #include "WindowManager.hpp" #include "cru/common/Logger.hpp" @@ -30,6 +31,8 @@ WinUiApplication::WinUiApplication() { log::Logger::GetInstance()->AddSource( std::make_unique<::cru::platform::win::WinDebugLoggerSource>()); + log::Logger::GetInstance()->AddSource( + std::make_unique<::cru::platform::win::WinStdOutLoggerSource>()); graph_factory_ = std::make_unique(); -- cgit v1.2.3 From c7c9c62fd3813a6230a2af7fc8c9882baa426a7f Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 28 Oct 2020 00:01:30 +0800 Subject: ... --- include/cru/common/Base.hpp | 13 +++++++---- include/cru/common/Event.hpp | 4 ++++ include/cru/ui/ShortcutHub.hpp | 36 ++++++++++++++++++++++++---- src/ui/ShortcutHub.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 8 deletions(-) (limited to 'include/cru/common') diff --git a/include/cru/common/Base.hpp b/include/cru/common/Base.hpp index a5a9421d..560f83bb 100644 --- a/include/cru/common/Base.hpp +++ b/include/cru/common/Base.hpp @@ -1,8 +1,8 @@ #pragma once #include "PreConfig.hpp" +#include #include - #include #define CRU_UNUSED(entity) static_cast(entity); @@ -42,12 +42,17 @@ struct Interface { virtual ~Interface() = default; }; -[[noreturn]] inline void UnreachableCode() { - throw std::runtime_error("Unreachable code."); -} +[[noreturn]] inline void UnreachableCode() { std::terminate(); } using Index = gsl::index; +// https://www.boost.org/doc/libs/1_54_0/doc/html/hash/reference.html#boost.hash_combine +template +inline void hash_combine(std::size_t& s, const T& v) { + std::hash h; + s ^= h(v) + 0x9e3779b9 + (s << 6) + (s >> 2); +} + #define CRU_DEFINE_CLASS_LOG_TAG(tag) \ private: \ constexpr static std::u16string_view log_tag = tag; diff --git a/include/cru/common/Event.hpp b/include/cru/common/Event.hpp index 6417bc78..93ab9b7a 100644 --- a/include/cru/common/Event.hpp +++ b/include/cru/common/Event.hpp @@ -232,6 +232,10 @@ class EventRevokerListGuard { return *this; } + void Clear() { event_revoker_guard_list_.clear(); } + + bool IsEmpty() const { return event_revoker_guard_list_.empty(); } + private: std::vector event_revoker_guard_list_; }; diff --git a/include/cru/ui/ShortcutHub.hpp b/include/cru/ui/ShortcutHub.hpp index 9995a4e1..7a75e4a1 100644 --- a/include/cru/ui/ShortcutHub.hpp +++ b/include/cru/ui/ShortcutHub.hpp @@ -1,12 +1,17 @@ #pragma once #include "Base.hpp" +#include "cru/common/Base.hpp" +#include "cru/common/Event.hpp" #include "cru/platform/native/Keyboard.hpp" +#include #include +#include #include #include #include +#include #include namespace cru::ui { @@ -42,8 +47,23 @@ class ShortcutKeyBind { platform::native::KeyCode key_; platform::native::KeyModifier modifier_; }; +} // namespace cru::ui + +namespace std { +template <> +struct hash { + std::size_t operator()(const cru::ui::ShortcutKeyBind& value) const { + std::size_t result = 0; + cru::hash_combine(result, value.GetKey()); + cru::hash_combine(result, value.GetModifier()); + return result; + } +}; +} // namespace std +namespace cru::ui { struct ShortcutInfo { + int id; std::u16string name; ShortcutKeyBind key_bind; std::function handler; @@ -51,15 +71,16 @@ struct ShortcutInfo { class ShortcutHub : public Object { public: - ShortcutHub(); + ShortcutHub() = default; CRU_DELETE_COPY(ShortcutHub) CRU_DELETE_MOVE(ShortcutHub) - ~ShortcutHub() override; + ~ShortcutHub() override = default; // Handler return true if it consumes the shortcut. Or return false if it does - // not handle the shortcut. Name is just for debug. + // not handle the shortcut. Name is just for debug. Return an id used for + // unregistering. int RegisterShortcut(std::u16string name, ShortcutKeyBind bind, std::function handler); @@ -67,12 +88,19 @@ class ShortcutHub : public Object { std::vector GetAllShortcuts() const; std::optional GetShortcut(int id) const; - std::vector GetShortcutByKeyBind( + const std::vector& GetShortcutByKeyBind( const ShortcutKeyBind& key_bind) const; void Install(Control* control); void Uninstall(); private: + std::unordered_map> map_; + + const std::vector empty_list_; + + int current_id_ = 1; + + EventRevokerListGuard event_guard_; }; } // namespace cru::ui diff --git a/src/ui/ShortcutHub.cpp b/src/ui/ShortcutHub.cpp index 2246b6eb..e5847f8c 100644 --- a/src/ui/ShortcutHub.cpp +++ b/src/ui/ShortcutHub.cpp @@ -1,5 +1,58 @@ #include "cru/ui/ShortcutHub.hpp" +#include +#include +#include namespace cru::ui { +int ShortcutHub::RegisterShortcut(std::u16string name, ShortcutKeyBind bind, + std::function handler) { + const int id = current_id_++; + ShortcutInfo info{id, std::move(name), bind, std::move(handler)}; + map_[bind].push_back(std::move(info)); + return id; +} + +void ShortcutHub::UnregisterShortcut(int id) { + if (id <= 0) return; + for (auto& pair : map_) { + auto& list = pair.second; + auto result = + std::find_if(list.cbegin(), list.cend(), + [id](const ShortcutInfo& info) { return info.id == id; }); + if (result != list.cend()) { + list.erase(result); + } + } +} + +std::vector ShortcutHub::GetAllShortcuts() const { + std::vector result; + + for (const auto& pair : map_) { + std::copy(pair.second.cbegin(), pair.second.cend(), + std::back_inserter(result)); + } + + return result; +} + +std::optional ShortcutHub::GetShortcut(int id) const { + for (auto& pair : map_) { + auto& list = pair.second; + auto result = + std::find_if(list.cbegin(), list.cend(), + [id](const ShortcutInfo& info) { return info.id == id; }); + if (result != list.cend()) { + return *result; + } + } + return std::nullopt; +} +const std::vector& ShortcutHub::GetShortcutByKeyBind( + const ShortcutKeyBind& key_bind) const { + auto result = map_.find(key_bind); + if (result != map_.cend()) return result->second; + return empty_list_; } +} // namespace cru::ui -- cgit v1.2.3 From 864b031211322dc276b220ec0a6e11483503a0e9 Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 28 Oct 2020 17:44:06 +0800 Subject: ... --- include/cru/common/StringUtil.hpp | 4 ++ src/common/StringUtil.cpp | 13 +++- src/ui/controls/TextControlService.hpp | 122 +++++++++++++++++++++++++-------- 3 files changed, 107 insertions(+), 32 deletions(-) (limited to 'include/cru/common') diff --git a/include/cru/common/StringUtil.hpp b/include/cru/common/StringUtil.hpp index 5dacfa12..276048f5 100644 --- a/include/cru/common/StringUtil.hpp +++ b/include/cru/common/StringUtil.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include "Base.hpp" namespace cru { @@ -124,4 +125,7 @@ void Utf16EncodeCodePointAppend(CodePoint code_point, std::u16string& str); std::string ToUtf8(std::u16string_view s); std::u16string ToUtf16(std::string_view s); + +// If given s is not a valid utf16 string, return value is UD. +bool Utf16IsValidInsertPosition(std::u16string_view s, gsl::index position); } // namespace cru diff --git a/src/common/StringUtil.cpp b/src/common/StringUtil.cpp index fc6d6349..3c312d49 100644 --- a/src/common/StringUtil.cpp +++ b/src/common/StringUtil.cpp @@ -1,4 +1,5 @@ #include "cru/common/StringUtil.hpp" +#include "gsl/gsl_util" namespace cru { namespace { @@ -191,8 +192,8 @@ void Utf8EncodeCodePointAppend(CodePoint code_point, std::string& str) { } void Utf16EncodeCodePointAppend(CodePoint code_point, std::u16string& str) { - if (code_point >= 0 && code_point <= 0xD7FF || - code_point >= 0xE000 && code_point <= 0xFFFF) { + if ((code_point >= 0 && code_point <= 0xD7FF) || + (code_point >= 0xE000 && code_point <= 0xFFFF)) { str.push_back(static_cast(code_point)); } else if (code_point >= 0x10000 && code_point <= 0x10FFFF) { std::uint32_t u = code_point - 0x10000; @@ -220,4 +221,12 @@ std::u16string ToUtf16(std::string_view s) { } return result; } + +bool Utf16IsValidInsertPosition(std::u16string_view s, gsl::index position) { + if (position < 0) return false; + if (position > static_cast(s.size())) return false; + if (position == 0) return true; + if (position == static_cast(s.size())) return true; + return !IsUtf16SurrogatePairTrailing(s[position]); +} } // namespace cru diff --git a/src/ui/controls/TextControlService.hpp b/src/ui/controls/TextControlService.hpp index c1a879f6..04807c30 100644 --- a/src/ui/controls/TextControlService.hpp +++ b/src/ui/controls/TextControlService.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include "../Helper.hpp" #include "cru/common/Logger.hpp" #include "cru/common/StringUtil.hpp" @@ -7,6 +8,7 @@ #include "cru/platform/native/InputMethod.hpp" #include "cru/platform/native/UiApplication.hpp" #include "cru/platform/native/Window.hpp" +#include "cru/ui/Base.hpp" #include "cru/ui/Control.hpp" #include "cru/ui/ShortcutHub.hpp" #include "cru/ui/UiEvent.hpp" @@ -14,6 +16,7 @@ #include "cru/ui/render/CanvasRenderObject.hpp" #include "cru/ui/render/ScrollRenderObject.hpp" #include "cru/ui/render/TextRenderObject.hpp" +#include "gsl/gsl_util" namespace cru::ui::controls { constexpr int k_default_caret_blink_duration = 500; @@ -64,13 +67,73 @@ class TextControlService : public Object { std::u16string_view GetTextView() { return this->text_; } void SetText(std::u16string text, bool stop_composition = false) { this->text_ = std::move(text); + CoerceSelection(); + if (stop_composition && this->input_method_context_) { + this->input_method_context_->CancelComposition(); + } + SyncTextRenderObject(); + } + + void InsertText(gsl::index position, std::u16string_view text, + bool stop_composition = false) { + if (!Utf16IsValidInsertPosition(this->text_, position)) { + log::TagError(log_tag, u"Invalid text insert position."); + return; + } + this->text_.insert(this->text_.cbegin() + position, text.begin(), + text.end()); if (stop_composition && this->input_method_context_) { this->input_method_context_->CancelComposition(); } - CoerceSelection(); SyncTextRenderObject(); } + void DeleteChar(gsl::index position, bool stop_composition = false) { + if (!Utf16IsValidInsertPosition(this->text_, position)) { + log::TagError(log_tag, u"Invalid text delete position."); + return; + } + if (position == static_cast(this->text_.size())) return; + Index next; + Utf16NextCodePoint(this->text_, position, &next); + this->DeleteText(TextRange::FromTwoSides(position, next), stop_composition); + } + + // Return the position of deleted character. + gsl::index DeleteCharPrevious(gsl::index position, + bool stop_composition = false) { + if (!Utf16IsValidInsertPosition(this->text_, position)) { + log::TagError(log_tag, u"Invalid text delete position."); + return 0; + } + if (position == 0) return 0; + Index previous; + Utf16PreviousCodePoint(this->text_, position, &previous); + this->DeleteText(TextRange::FromTwoSides(previous, position), + stop_composition); + return previous; + } + + void DeleteText(TextRange range, bool stop_composition = false) { + if (range.count == 0) return; + range = range.Normalize(); + if (!Utf16IsValidInsertPosition(this->text_, range.GetStart())) { + log::TagError(log_tag, u"Invalid text delete start position."); + return; + } + if (!Utf16IsValidInsertPosition(this->text_, range.GetStart())) { + log::TagError(log_tag, u"Invalid text delete end position."); + return; + } + this->text_.erase(this->text_.cbegin() + range.GetStart(), + this->text_.cbegin() + range.GetEnd()); + this->CoerceSelection(); + if (stop_composition && this->input_method_context_) { + this->input_method_context_->CancelComposition(); + } + this->SyncTextRenderObject(); + } + std::optional GetCompositionInfo() { if (this->input_method_context_ == nullptr) return std::nullopt; auto composition_info = this->input_method_context_->GetCompositionText(); @@ -126,21 +189,33 @@ class TextControlService : public Object { CoerceSelection(); SyncTextRenderObject(); if (scroll_to_caret) { - if (const auto scroll_render_object = this->GetScrollRenderObject()) { - const auto caret_rect = this->GetTextRenderObject()->GetCaretRect(); - // TODO: Wait a tick for layout completed. - this->GetScrollRenderObject()->ScrollToContain(caret_rect, - Thickness{5.f}); - } + this->ScrollToCaret(); } } void DeleteSelectedText() { - auto selection = GetSelection().Normalize(); - if (selection.count == 0) return; - this->text_.erase(this->text_.cbegin() + selection.GetStart(), - this->text_.cbegin() + selection.GetEnd()); - SetSelection(selection.GetStart()); + this->DeleteText(GetSelection()); + SetSelection(GetSelection().GetStart()); + } + + // If some text is selected, then they are deleted first. Then insert text + // into caret position. + void ReplaceSelectedText(std::u16string_view text) { + DeleteSelectedText(); + InsertText(GetSelection().GetStart(), text); + SetSelection(GetSelection().GetStart() + text.size()); + } + + void ScrollToCaret(bool next_tick = true) { + if (next_tick) { + scroll_to_caret_timer_canceler_.Reset( + GetUiApplication()->GetInstance()->SetImmediate( + [this]() { this->ScrollToCaret(false); })); + } else { + const auto caret_rect = this->GetTextRenderObject()->GetCaretRect(); + this->GetScrollRenderObject()->ScrollToContain(caret_rect, + Thickness{5.f}); + } } private: @@ -278,14 +353,7 @@ class TextControlService : public Object { if (!IsEditable()) return; const auto selection = GetSelection(); if (selection.count == 0) { - const auto text = this->GetTextView(); - const auto caret_position = GetCaretPosition(); - if (caret_position == 0) return; - gsl::index new_position; - Utf16PreviousCodePoint(text, caret_position, &new_position); - text_.erase(text_.cbegin() + new_position, - text_.cbegin() + caret_position); - SetSelection(new_position); + SetSelection(DeleteCharPrevious(GetCaretPosition())); } else { this->DeleteSelectedText(); } @@ -294,14 +362,7 @@ class TextControlService : public Object { if (!IsEditable()) return; const auto selection = GetSelection(); if (selection.count == 0) { - const auto text = this->GetTextView(); - const auto caret_position = GetCaretPosition(); - if (caret_position == static_cast(text.size())) return; - gsl::index new_position; - Utf16NextCodePoint(text, caret_position, &new_position); - text_.erase(text_.cbegin() + caret_position, - text_.cbegin() + new_position); - SyncTextRenderObject(); + DeleteChar(GetCaretPosition()); } else { this->DeleteSelectedText(); } @@ -362,8 +423,7 @@ class TextControlService : public Object { input_method_context_->TextEvent()->AddHandler( [this](const std::u16string_view& text) { if (text == u"\b") return; - this->text_.insert(GetCaretPosition(), text); - this->SetSelection(GetCaretPosition() + text.size()); + this->ReplaceSelectedText(text); }); } } @@ -393,6 +453,8 @@ class TextControlService : public Object { ShortcutHub shortcut_hub_; + platform::native::TimerAutoCanceler scroll_to_caret_timer_canceler_; + // nullopt means not selecting std::optional select_down_button_; -- cgit v1.2.3 From 0fb7a0e0b2b9e04ca414b1e47c69cc854c79831b Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 1 Dec 2020 23:20:01 +0800 Subject: ... --- include/cru/common/HandlerRegistry.hpp | 86 +++++++++++++++++++++++++++ include/cru/ui/controls/Button.hpp | 12 +++- include/cru/ui/controls/IClickableControl.hpp | 12 ++++ include/cru/ui/helper/Styler.hpp | 47 --------------- include/cru/ui/style/Styler.hpp | 0 include/cru/ui/style/Trigger.hpp | 75 +++++++++++++++++++++++ src/ui/CMakeLists.txt | 4 +- src/ui/helper/Styler.cpp | 27 --------- src/ui/style/Styler.cpp | 0 src/ui/style/Trigger.cpp | 49 +++++++++++++++ test/common/HandlerRegistryTest.cpp | 36 +++++++++++ 11 files changed, 271 insertions(+), 77 deletions(-) create mode 100644 include/cru/common/HandlerRegistry.hpp create mode 100644 include/cru/ui/controls/IClickableControl.hpp delete mode 100644 include/cru/ui/helper/Styler.hpp create mode 100644 include/cru/ui/style/Styler.hpp create mode 100644 include/cru/ui/style/Trigger.hpp delete mode 100644 src/ui/helper/Styler.cpp create mode 100644 src/ui/style/Styler.cpp create mode 100644 src/ui/style/Trigger.cpp create mode 100644 test/common/HandlerRegistryTest.cpp (limited to 'include/cru/common') diff --git a/include/cru/common/HandlerRegistry.hpp b/include/cru/common/HandlerRegistry.hpp new file mode 100644 index 00000000..bd74a9e0 --- /dev/null +++ b/include/cru/common/HandlerRegistry.hpp @@ -0,0 +1,86 @@ +#pragma once +#include "Base.hpp" + +#include +#include +#include +#include + +namespace cru { + +template +class HandlerRegistryIterator { + public: + using RawIterator = + typename std::vector>>::const_iterator; + + explicit HandlerRegistryIterator(RawIterator raw) : raw_(std::move(raw)) {} + + CRU_DELETE_COPY(HandlerRegistryIterator) + CRU_DELETE_MOVE(HandlerRegistryIterator) + + ~HandlerRegistryIterator() = default; + + const std::function& operator*() const { return raw_->second; } + const std::function* operator->() const { return &raw_->second; } + + HandlerRegistryIterator& operator++() { + ++raw_; + return *this; + } + + HandlerRegistryIterator operator++(int) { + auto c = *this; + this->operator++(); + return c; + } + + bool operator==(const HandlerRegistryIterator& other) const { + return this->raw_ == other.raw_; + } + + bool operator!=(const HandlerRegistryIterator& other) const { + return !this->operator==(other); + } + + private: + RawIterator raw_; +}; + +template +class HandlerRegistry final { + public: + HandlerRegistry() = default; + CRU_DEFAULT_COPY(HandlerRegistry) + CRU_DEFAULT_MOVE(HandlerRegistry) + ~HandlerRegistry() = default; + + public: + int AddHandler(std::function handler) { + auto id = current_id_++; + handler_list_.push_back({id, std::move(handler)}); + } + + void RemoveHandler(int id) { + auto result = std::find_if(handler_list_.cbegin(), handler_list_.cend(), + [id](const std::pair>& d) { + return d.first == id; + }); + if (result != handler_list_.cend()) { + handler_list_.erase(result); + } + } + + HandlerRegistryIterator begin() const { + return HandlerRegistryIterator(handler_list_.begin()); + } + + HandlerRegistryIterator end() const { + return HandlerRegistryIterator(handler_list_.begin()); + } + + private: + int current_id_ = 1; + std::vector>> handler_list_; +}; +} // namespace cru diff --git a/include/cru/ui/controls/Button.hpp b/include/cru/ui/controls/Button.hpp index 5619ec89..0d2f4898 100644 --- a/include/cru/ui/controls/Button.hpp +++ b/include/cru/ui/controls/Button.hpp @@ -2,9 +2,11 @@ #include "ContentControl.hpp" #include "../helper/ClickDetector.hpp" +#include "IClickableControl.hpp" +#include "cru/common/Event.hpp" namespace cru::ui::controls { -class Button : public ContentControl { +class Button : public ContentControl, public virtual IClickableControl { public: static constexpr std::u16string_view control_type = u"Button"; @@ -25,6 +27,14 @@ class Button : public ContentControl { render::RenderObject* GetRenderObject() const override; public: + helper::ClickState GetClickState() override { + return click_detector_.GetState(); + } + + IEvent* ClickStateChangeEvent() override { + return click_detector_.StateChangeEvent(); + } + const ButtonStyle& GetStyle() const { return style_; } void SetStyle(ButtonStyle style); diff --git a/include/cru/ui/controls/IClickableControl.hpp b/include/cru/ui/controls/IClickableControl.hpp new file mode 100644 index 00000000..aa7f13ab --- /dev/null +++ b/include/cru/ui/controls/IClickableControl.hpp @@ -0,0 +1,12 @@ +#pragma once +#include "Base.hpp" + +#include "cru/common/Event.hpp" +#include "cru/ui/helper/ClickDetector.hpp" + +namespace cru::ui::controls { +struct IClickableControl : virtual Interface { + virtual helper::ClickState GetClickState() = 0; + virtual IEvent* ClickStateChangeEvent() = 0; +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/helper/Styler.hpp b/include/cru/ui/helper/Styler.hpp deleted file mode 100644 index ed8bfbdc..00000000 --- a/include/cru/ui/helper/Styler.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once -#include "cru/common/Base.hpp" -#include "cru/common/Event.hpp" -#include "cru/ui/Base.hpp" -#include "cru/ui/helper/ClickDetector.hpp" -#include "gsl/pointers" - -#include - -namespace cru::ui::helper { -struct ControlStyleState { - ClickState click_state; - bool focus; -}; - -class Styler : public Object { - public: - // You could provide your click detector. Otherwise a new one will be created. - explicit Styler(gsl::not_null control, - ClickDetector* click_detector = nullptr); - - CRU_DELETE_COPY(Styler) - CRU_DELETE_MOVE(Styler) - - ~Styler(); - - public: - gsl::not_null GetControl() const { return control_; } - gsl::not_null GetClickDetector() const { - return click_detector_; - } - - IEvent* StateChangeEvent() { return &state_change_event_; } - - private: - void RaiseStateChangeEvent(); - - private: - gsl::not_null control_; - std::unique_ptr managed_click_detector_; - gsl::not_null click_detector_; - - Event state_change_event_; - - EventRevokerListGuard event_guard_; -}; -} // namespace cru::ui::helper diff --git a/include/cru/ui/style/Styler.hpp b/include/cru/ui/style/Styler.hpp new file mode 100644 index 00000000..e69de29b diff --git a/include/cru/ui/style/Trigger.hpp b/include/cru/ui/style/Trigger.hpp new file mode 100644 index 00000000..ab012a3e --- /dev/null +++ b/include/cru/ui/style/Trigger.hpp @@ -0,0 +1,75 @@ +#pragma once +#include "../Base.hpp" +#include "cru/common/Base.hpp" +#include "cru/common/Event.hpp" +#include "cru/ui/controls/IClickableControl.hpp" +#include "cru/ui/helper/ClickDetector.hpp" + +#include +#include + +namespace cru::ui::style { +class Trigger { + public: + virtual ~Trigger() = default; + + bool GetState() const { return current_; } + IEvent* ChangeEvent() { return &change_event_; } + + protected: + void Raise(bool value); + + private: + bool current_ = false; + Event change_event_; + + protected: + EventRevokerListGuard guard_; +}; + +class CompoundTrigger : public Trigger { + public: + explicit CompoundTrigger(std::vector triggers); + + const std::vector& GetTriggers() const { return triggers_; } + + protected: + virtual bool CalculateState(const std::vector& triggers) const = 0; + + private: + std::vector triggers_; +}; + +class AndTrigger : public CompoundTrigger { + public: + using CompoundTrigger::CompoundTrigger; + + protected: + bool CalculateState(const std::vector& triggers) const override; +}; + +class OrTrigger : public CompoundTrigger { + public: + using CompoundTrigger::CompoundTrigger; + + protected: + bool CalculateState(const std::vector& triggers) const override; +}; + +class FocusTrigger : public Trigger { + public: + FocusTrigger(controls::Control* control, bool has_focus); + + private: + bool has_focus_; +}; + +class ClickStateTrigger : public Trigger { + public: + ClickStateTrigger(controls::IClickableControl* control, + helper::ClickState click_state); + + private: + helper::ClickState click_state_; +}; +} // namespace cru::ui::style diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 974a3959..5cb50ce3 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -24,7 +24,6 @@ add_library(cru_ui STATIC helper/BorderStyle.cpp helper/ClickDetector.cpp helper/ShortcutHub.cpp - helper/Styler.cpp host/LayoutPaintCycler.cpp host/WindowHost.cpp render/BorderRenderObject.cpp @@ -35,6 +34,7 @@ add_library(cru_ui STATIC render/ScrollRenderObject.cpp render/StackLayoutRenderObject.cpp render/TextRenderObject.cpp + style/Trigger.cpp ) target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/Base.hpp @@ -58,7 +58,6 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/helper/BorderStyle.hpp ${CRU_UI_INCLUDE_DIR}/helper/ClickDetector.hpp ${CRU_UI_INCLUDE_DIR}/helper/ShortcutHub.hpp - ${CRU_UI_INCLUDE_DIR}/helper/Styler.hpp ${CRU_UI_INCLUDE_DIR}/host/LayoutPaintCycler.hpp ${CRU_UI_INCLUDE_DIR}/host/WindowHost.hpp ${CRU_UI_INCLUDE_DIR}/render/Base.hpp @@ -72,5 +71,6 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/render/ScrollRenderObject.hpp ${CRU_UI_INCLUDE_DIR}/render/StackLayoutRenderObject.hpp ${CRU_UI_INCLUDE_DIR}/render/TextRenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/style/Trigger.hpp ) target_link_libraries(cru_ui PUBLIC cru_platform_gui) diff --git a/src/ui/helper/Styler.cpp b/src/ui/helper/Styler.cpp deleted file mode 100644 index 6500a3f7..00000000 --- a/src/ui/helper/Styler.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "cru/ui/helper/Styler.hpp" -#include "cru/ui/helper/ClickDetector.hpp" -#include "gsl/pointers" - -namespace cru::ui::helper { -Styler::Styler(gsl::not_null control, - ClickDetector* click_detector) - : control_(control), - managed_click_detector_(click_detector ? nullptr - : new ClickDetector(control)), - click_detector_(click_detector ? click_detector - : managed_click_detector_.get()) { - event_guard_ += control_->GainFocusEvent()->Direct()->AddHandler( - [this](auto) { this->RaiseStateChangeEvent(); }); - event_guard_ += control_->LoseFocusEvent()->Direct()->AddHandler( - [this](auto) { this->RaiseStateChangeEvent(); }); - event_guard_ += click_detector_->StateChangeEvent()->AddHandler( - [this](auto) { this->RaiseStateChangeEvent(); }); -} - -Styler::~Styler() = default; - -void Styler::RaiseStateChangeEvent() { - this->state_change_event_.Raise(ControlStyleState{ - this->click_detector_->GetState(), this->control_->HasFocus()}); -} -} // namespace cru::ui::helper diff --git a/src/ui/style/Styler.cpp b/src/ui/style/Styler.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/ui/style/Trigger.cpp b/src/ui/style/Trigger.cpp new file mode 100644 index 00000000..b7292ce3 --- /dev/null +++ b/src/ui/style/Trigger.cpp @@ -0,0 +1,49 @@ +#include "cru/ui/style/Trigger.hpp" + +#include "cru/ui/controls/Control.hpp" +#include "cru/ui/helper/ClickDetector.hpp" + +namespace cru::ui::style { +void Trigger::Raise(bool value) { + if (value == current_) return; + current_ = value; + change_event_.Raise(value); +} + +CompoundTrigger::CompoundTrigger(std::vector triggers) + : triggers_(std::move(triggers)) { + for (auto trigger : triggers_) { + guard_ += trigger->ChangeEvent()->AddHandler( + [this](bool) { Raise(this->CalculateState(triggers_)); }); + } +} + +bool AndTrigger::CalculateState(const std::vector& triggers) const { + for (auto trigger : triggers) { + if (!trigger->GetState()) return false; + } + return true; +} + +bool OrTrigger::CalculateState(const std::vector& triggers) const { + for (auto trigger : triggers) { + if (trigger->GetState()) return true; + } + return false; +} + +FocusTrigger::FocusTrigger(controls::Control* control, bool has_focus) + : has_focus_(has_focus) { + guard_ += control->GainFocusEvent()->Direct()->AddHandler( + [this](auto) { Raise(has_focus_); }); + guard_ += control->LoseFocusEvent()->Direct()->AddHandler( + [this](auto) { Raise(!has_focus_); }); +} + +ClickStateTrigger::ClickStateTrigger(controls::IClickableControl* control, + helper::ClickState click_state) + : click_state_(click_state) { + guard_ += control->ClickStateChangeEvent()->AddHandler( + [this](helper::ClickState cs) { Raise(cs == click_state_); }); +} +} // namespace cru::ui::style diff --git a/test/common/HandlerRegistryTest.cpp b/test/common/HandlerRegistryTest.cpp new file mode 100644 index 00000000..d1792c7c --- /dev/null +++ b/test/common/HandlerRegistryTest.cpp @@ -0,0 +1,36 @@ +#include "cru/common/HandlerRegistry.hpp" + +#include +#include + +TEST(HandlerRegistry, Work) { + using namespace cru; + HandlerRegistry registry; + + int counter = 1; + + auto tag1 = registry.AddHandler([&counter] { counter++; }); + auto tag2 = registry.AddHandler([&counter] { counter++; }); + + for (const auto& handler : registry) { + handler(); + } + + ASSERT_EQ(counter, 3); + + registry.RemoveHandler(tag1); + + for (const auto& handler : registry) { + handler(); + } + + ASSERT_EQ(counter, 4); + + registry.RemoveHandler(tag2); + + for (const auto& handler : registry) { + handler(); + } + + ASSERT_EQ(counter, 4); +} -- cgit v1.2.3 From 145cfd5b82d76e0c937eceda707aa22427899943 Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 2 Dec 2020 19:19:22 +0800 Subject: ... --- include/cru/common/Event.hpp | 72 +++++++++++++++++++++++---------- include/cru/common/SelfResolvable.hpp | 24 +++++++++-- include/cru/ui/style/Condition.hpp | 69 ++++++++++++++++++++++++++++++++ include/cru/ui/style/Trigger.hpp | 75 ----------------------------------- src/ui/CMakeLists.txt | 4 +- src/ui/style/Condition.cpp | 71 +++++++++++++++++++++++++++++++++ src/ui/style/Trigger.cpp | 49 ----------------------- 7 files changed, 213 insertions(+), 151 deletions(-) create mode 100644 include/cru/ui/style/Condition.hpp delete mode 100644 include/cru/ui/style/Trigger.hpp create mode 100644 src/ui/style/Condition.cpp delete mode 100644 src/ui/style/Trigger.cpp (limited to 'include/cru/common') diff --git a/include/cru/common/Event.hpp b/include/cru/common/Event.hpp index 93ab9b7a..59502527 100644 --- a/include/cru/common/Event.hpp +++ b/include/cru/common/Event.hpp @@ -8,12 +8,16 @@ #include #include #include +#include #include namespace cru { class EventRevoker; namespace details { +template +inline constexpr bool always_false_v = false; + // Base class of all Event. // It erases event args types and provides a // unified form to create event revoker and @@ -25,10 +29,8 @@ class EventBase : public SelfResolvable { using EventHandlerToken = long; EventBase() {} - EventBase(const EventBase& other) = delete; - EventBase(EventBase&& other) = delete; - EventBase& operator=(const EventBase& other) = delete; - EventBase& operator=(EventBase&& other) = delete; + CRU_DELETE_COPY(EventBase) + CRU_DEFAULT_MOVE(EventBase) virtual ~EventBase() = default; // Remove the handler with the given token. If the token @@ -85,59 +87,73 @@ using DeducedEventArgs = std::conditional_t< std::is_lvalue_reference_v, TRaw, std::conditional_t, TRaw, const TRaw&>>; +struct IBaseEvent { + protected: + IBaseEvent() = default; + CRU_DELETE_COPY(IBaseEvent) + CRU_DEFAULT_MOVE(IBaseEvent) + ~IBaseEvent() = default; // Note that user can't destroy a Event via IEvent. + // So destructor should be protected. + + using SpyOnlyHandler = std::function; + + public: + virtual EventRevoker AddHandler(SpyOnlyHandler handler) = 0; +}; + // Provides an interface of event. // IEvent only allow to add handler but not to raise the event. You may // want to create an Event object and expose IEvent only so users won't // be able to emit the event. template -struct IEvent { +struct IEvent : virtual IBaseEvent { public: using EventArgs = DeducedEventArgs; using EventHandler = std::function; + using HandlerVariant = std::variant; protected: IEvent() = default; - IEvent(const IEvent& other) = delete; - IEvent(IEvent&& other) = delete; - IEvent& operator=(const IEvent& other) = delete; - IEvent& operator=(IEvent&& other) = delete; + CRU_DELETE_COPY(IEvent) + CRU_DEFAULT_MOVE(IEvent) ~IEvent() = default; // Note that user can't destroy a Event via IEvent. So // destructor should be protected. public: - virtual EventRevoker AddHandler(const EventHandler& handler) = 0; - virtual EventRevoker AddHandler(EventHandler&& handler) = 0; + virtual EventRevoker AddHandler(EventHandler handler) = 0; }; // A non-copyable non-movable Event class. // It stores a list of event handlers. template class Event : public details::EventBase, public IEvent { + using typename IBaseEvent::SpyOnlyHandler; using typename IEvent::EventHandler; + using typename IEvent::HandlerVariant; private: struct HandlerData { HandlerData(EventHandlerToken token, EventHandler handler) : token(token), handler(handler) {} + HandlerData(EventHandlerToken token, SpyOnlyHandler handler) + : token(token), handler(handler) {} EventHandlerToken token; - EventHandler handler; + HandlerVariant handler; }; public: Event() = default; - Event(const Event&) = delete; - Event& operator=(const Event&) = delete; - Event(Event&&) = delete; - Event& operator=(Event&&) = delete; + CRU_DELETE_COPY(Event) + CRU_DEFAULT_MOVE(Event) ~Event() = default; - EventRevoker AddHandler(const EventHandler& handler) override { + EventRevoker AddHandler(SpyOnlyHandler handler) override { const auto token = current_token_++; - this->handler_data_list_.emplace_back(token, handler); + this->handler_data_list_.emplace_back(token, std::move(handler)); return CreateRevoker(token); } - EventRevoker AddHandler(EventHandler&& handler) override { + EventRevoker AddHandler(EventHandler handler) override { const auto token = current_token_++; this->handler_data_list_.emplace_back(token, std::move(handler)); return CreateRevoker(token); @@ -150,13 +166,25 @@ class Event : public details::EventBase, public IEvent { // is called, so even if you delete a handler during this period, all handlers // in the snapshot will be executed. void Raise(typename IEvent::EventArgs args) { - std::vector handlers; + std::vector handlers; handlers.reserve(this->handler_data_list_.size()); for (const auto& data : this->handler_data_list_) { handlers.push_back(data.handler); } - for (const auto& handler : handlers) { - handler(args); + for (const auto& handler_variant : handlers) { + std::visit( + [&args](auto&& handler) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + handler(); + } else if constexpr (std::is_same_v) { + handler(args); + } else { + static_assert(details::always_false_v, + "non-exhaustive visitor!"); + } + }, + handler_variant); } } diff --git a/include/cru/common/SelfResolvable.hpp b/include/cru/common/SelfResolvable.hpp index 94f3ae87..eaa4ce34 100644 --- a/include/cru/common/SelfResolvable.hpp +++ b/include/cru/common/SelfResolvable.hpp @@ -39,9 +39,27 @@ class SelfResolvable { SelfResolvable() : resolver_(new T*(static_cast(this))) {} SelfResolvable(const SelfResolvable&) = delete; SelfResolvable& operator=(const SelfResolvable&) = delete; - SelfResolvable(SelfResolvable&&) = delete; - SelfResolvable& operator=(SelfResolvable&&) = delete; - virtual ~SelfResolvable() { (*resolver_) = nullptr; } + + // Resolvers to old object will resolve to new object. + SelfResolvable(SelfResolvable&& other) + : resolver_(std::move(other.resolver_)) { + (*resolver_) = static_cast(this); + } + + // Old resolvers for this object will resolve to nullptr. + // Other's resolvers will now resolve to this. + SelfResolvable& operator=(SelfResolvable&& other) { + if (this != &other) { + (*resolver_) = nullptr; + resolver_ = std::move(other.resolver_); + (*resolver_) = static_cast(this); + } + return *this; + } + + virtual ~SelfResolvable() { + if (resolver_ != nullptr) (*resolver_) = nullptr; + } ObjectResolver CreateResolver() { return ObjectResolver(resolver_); } diff --git a/include/cru/ui/style/Condition.hpp b/include/cru/ui/style/Condition.hpp new file mode 100644 index 00000000..b88a338f --- /dev/null +++ b/include/cru/ui/style/Condition.hpp @@ -0,0 +1,69 @@ +#pragma once +#include "../Base.hpp" +#include "cru/common/Base.hpp" +#include "cru/common/Event.hpp" +#include "cru/ui/controls/IClickableControl.hpp" +#include "cru/ui/helper/ClickDetector.hpp" + +#include +#include +#include + +namespace cru::ui::style { +class Condition { + public: + virtual ~Condition() = default; + + virtual std::vector ChangeOn( + controls::Control* control) const = 0; + virtual bool Judge(controls::Control* control) const = 0; +}; + +class CompoundCondition : public Condition { + public: + explicit CompoundCondition(std::vector conditions); + + const std::vector& GetConditions() const { return conditions_; } + + std::vector ChangeOn(controls::Control* control) const override; + + private: + std::vector conditions_; +}; + +class AndCondition : public CompoundCondition { + public: + using CompoundCondition::CompoundCondition; + + bool Judge(controls::Control* control) const override; +}; + +class OrCondition : public CompoundCondition { + public: + using CompoundCondition::CompoundCondition; + + bool Judge(controls::Control* control) const override; +}; + +class FocusCondition : public Condition { + public: + explicit FocusCondition(bool has_focus); + + std::vector ChangeOn(controls::Control* control) const override; + bool Judge(controls::Control* control) const override; + + private: + bool has_focus_; +}; + +class ClickStateCondition : public Condition { + public: + explicit ClickStateCondition(helper::ClickState click_state); + + std::vector ChangeOn(controls::Control* control) const override; + bool Judge(controls::Control* control) const override; + + private: + helper::ClickState click_state_; +}; +} // namespace cru::ui::style diff --git a/include/cru/ui/style/Trigger.hpp b/include/cru/ui/style/Trigger.hpp deleted file mode 100644 index ab012a3e..00000000 --- a/include/cru/ui/style/Trigger.hpp +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once -#include "../Base.hpp" -#include "cru/common/Base.hpp" -#include "cru/common/Event.hpp" -#include "cru/ui/controls/IClickableControl.hpp" -#include "cru/ui/helper/ClickDetector.hpp" - -#include -#include - -namespace cru::ui::style { -class Trigger { - public: - virtual ~Trigger() = default; - - bool GetState() const { return current_; } - IEvent* ChangeEvent() { return &change_event_; } - - protected: - void Raise(bool value); - - private: - bool current_ = false; - Event change_event_; - - protected: - EventRevokerListGuard guard_; -}; - -class CompoundTrigger : public Trigger { - public: - explicit CompoundTrigger(std::vector triggers); - - const std::vector& GetTriggers() const { return triggers_; } - - protected: - virtual bool CalculateState(const std::vector& triggers) const = 0; - - private: - std::vector triggers_; -}; - -class AndTrigger : public CompoundTrigger { - public: - using CompoundTrigger::CompoundTrigger; - - protected: - bool CalculateState(const std::vector& triggers) const override; -}; - -class OrTrigger : public CompoundTrigger { - public: - using CompoundTrigger::CompoundTrigger; - - protected: - bool CalculateState(const std::vector& triggers) const override; -}; - -class FocusTrigger : public Trigger { - public: - FocusTrigger(controls::Control* control, bool has_focus); - - private: - bool has_focus_; -}; - -class ClickStateTrigger : public Trigger { - public: - ClickStateTrigger(controls::IClickableControl* control, - helper::ClickState click_state); - - private: - helper::ClickState click_state_; -}; -} // namespace cru::ui::style diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 5cb50ce3..85c87f5e 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -34,7 +34,7 @@ add_library(cru_ui STATIC render/ScrollRenderObject.cpp render/StackLayoutRenderObject.cpp render/TextRenderObject.cpp - style/Trigger.cpp + style/Condition.cpp ) target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/Base.hpp @@ -71,6 +71,6 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/render/ScrollRenderObject.hpp ${CRU_UI_INCLUDE_DIR}/render/StackLayoutRenderObject.hpp ${CRU_UI_INCLUDE_DIR}/render/TextRenderObject.hpp - ${CRU_UI_INCLUDE_DIR}/style/Trigger.hpp + ${CRU_UI_INCLUDE_DIR}/style/Condition.hpp ) target_link_libraries(cru_ui PUBLIC cru_platform_gui) diff --git a/src/ui/style/Condition.cpp b/src/ui/style/Condition.cpp new file mode 100644 index 00000000..bc24e265 --- /dev/null +++ b/src/ui/style/Condition.cpp @@ -0,0 +1,71 @@ +#include "cru/ui/style/Condition.hpp" + +#include "cru/common/Event.hpp" +#include "cru/ui/controls/Control.hpp" +#include "cru/ui/controls/IClickableControl.hpp" +#include "cru/ui/helper/ClickDetector.hpp" + +namespace cru::ui::style { +CompoundCondition::CompoundCondition(std::vector conditions) + : conditions_(std::move(conditions)) {} + +std::vector CompoundCondition::ChangeOn( + controls::Control* control) const { + std::vector result; + + for (auto condition : GetConditions()) { + for (auto e : condition->ChangeOn(control)) { + result.push_back(e); + } + } + + return result; +} + +bool AndCondition::Judge(controls::Control* control) const { + for (auto condition : GetConditions()) { + if (!condition->Judge(control)) return false; + } + return true; +} + +bool OrCondition::Judge(controls::Control* control) const { + for (auto condition : GetConditions()) { + if (condition->Judge(control)) return true; + } + return false; +} + +FocusCondition::FocusCondition(bool has_focus) : has_focus_(has_focus) {} + +std::vector FocusCondition::ChangeOn( + controls::Control* control) const { + return {control->GainFocusEvent()->Direct(), + control->LoseFocusEvent()->Direct()}; +} + +bool FocusCondition::Judge(controls::Control* control) const { + return control->HasFocus() == has_focus_; +} + +ClickStateCondition::ClickStateCondition(helper::ClickState click_state) + : click_state_(click_state) {} + +std::vector ClickStateCondition::ChangeOn( + controls::Control* control) const { + auto clickable_control = dynamic_cast(control); + if (clickable_control) { + return {clickable_control->ClickStateChangeEvent()}; + } else { + return {}; + } +} + +bool ClickStateCondition::Judge(controls::Control* control) const { + auto clickable_control = dynamic_cast(control); + if (clickable_control) { + return clickable_control->GetClickState() == click_state_; + } + return false; +} +} // namespace cru::ui::style diff --git a/src/ui/style/Trigger.cpp b/src/ui/style/Trigger.cpp deleted file mode 100644 index b7292ce3..00000000 --- a/src/ui/style/Trigger.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "cru/ui/style/Trigger.hpp" - -#include "cru/ui/controls/Control.hpp" -#include "cru/ui/helper/ClickDetector.hpp" - -namespace cru::ui::style { -void Trigger::Raise(bool value) { - if (value == current_) return; - current_ = value; - change_event_.Raise(value); -} - -CompoundTrigger::CompoundTrigger(std::vector triggers) - : triggers_(std::move(triggers)) { - for (auto trigger : triggers_) { - guard_ += trigger->ChangeEvent()->AddHandler( - [this](bool) { Raise(this->CalculateState(triggers_)); }); - } -} - -bool AndTrigger::CalculateState(const std::vector& triggers) const { - for (auto trigger : triggers) { - if (!trigger->GetState()) return false; - } - return true; -} - -bool OrTrigger::CalculateState(const std::vector& triggers) const { - for (auto trigger : triggers) { - if (trigger->GetState()) return true; - } - return false; -} - -FocusTrigger::FocusTrigger(controls::Control* control, bool has_focus) - : has_focus_(has_focus) { - guard_ += control->GainFocusEvent()->Direct()->AddHandler( - [this](auto) { Raise(has_focus_); }); - guard_ += control->LoseFocusEvent()->Direct()->AddHandler( - [this](auto) { Raise(!has_focus_); }); -} - -ClickStateTrigger::ClickStateTrigger(controls::IClickableControl* control, - helper::ClickState click_state) - : click_state_(click_state) { - guard_ += control->ClickStateChangeEvent()->AddHandler( - [this](helper::ClickState cs) { Raise(cs == click_state_); }); -} -} // namespace cru::ui::style -- cgit v1.2.3 From 93a8bf8b967817031cd2798cdaedfa73f867dead Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 3 Dec 2020 20:51:53 +0800 Subject: ... --- include/cru/common/ClonablePtr.hpp | 201 ++++++++++++++++++++++++++++++++++ include/cru/ui/style/Condition.hpp | 33 ++---- include/cru/ui/style/StyleRule.hpp | 32 ++---- include/cru/ui/style/StyleRuleSet.hpp | 0 include/cru/ui/style/Styler.hpp | 6 +- src/ui/CMakeLists.txt | 2 + src/ui/style/Condition.cpp | 22 +--- src/ui/style/StyleRule.cpp | 6 + src/ui/style/StyleRuleSet.cpp | 0 9 files changed, 238 insertions(+), 64 deletions(-) create mode 100644 include/cru/common/ClonablePtr.hpp create mode 100644 include/cru/ui/style/StyleRuleSet.hpp create mode 100644 src/ui/style/StyleRuleSet.cpp (limited to 'include/cru/common') diff --git a/include/cru/common/ClonablePtr.hpp b/include/cru/common/ClonablePtr.hpp new file mode 100644 index 00000000..47a1d3bd --- /dev/null +++ b/include/cru/common/ClonablePtr.hpp @@ -0,0 +1,201 @@ +#pragma once + +#include +#include +#include +#include + +namespace cru { +template +class ClonablePtr { + public: + using element_type = typename std::unique_ptr::element_type; + using pointer = typename std::unique_ptr::pointer; + + ClonablePtr() = default; + ClonablePtr(std::nullptr_t) noexcept : ptr_(nullptr) {} + ClonablePtr(pointer p) noexcept : ptr_(p) {} + ClonablePtr(std::unique_ptr&& p) noexcept + : ptr_(std::move(p)) {} + template ::pointer, pointer>, + int> = 0> + ClonablePtr(std::unique_ptr&& p) : ptr_(std::move(p)) {} + ClonablePtr(const ClonablePtr& other) : ptr_(other.ptr_->Clone()) {} + ClonablePtr(ClonablePtr&& other) = default; + template ::pointer, pointer>, + int> = 0> + ClonablePtr(const ClonablePtr& other) : ptr_(other.ptr_->Clone()) {} + template ::pointer, pointer>, + int> = 0> + ClonablePtr(ClonablePtr&& other) noexcept : ptr_(std::move(other.ptr_)) {} + ClonablePtr& operator=(std::nullptr_t) noexcept { + ptr_ = nullptr; + return *this; + } + ClonablePtr& operator=(std::unique_ptr&& other) noexcept { + ptr_ = std::move(other); + return *this; + } + template ::pointer, pointer>, + int> = 0> + ClonablePtr& operator=(std::unique_ptr&& p) noexcept { + ptr_ = std::move(p); + return *this; + } + ClonablePtr& operator=(const ClonablePtr& other) { + if (this != &other) { + ptr_ = std::unique_ptr(other.ptr->Clone()); + } + return *this; + } + ClonablePtr& operator=(ClonablePtr&& other) = default; + template ::pointer, pointer>, + int> = 0> + ClonablePtr& operator=(const ClonablePtr& other) noexcept { + if (this != &other) { + ptr_ = std::unique_ptr(other.ptr_->Clone()); + } + return *this; + } + template ::pointer, pointer>, + int> = 0> + ClonablePtr& operator=(ClonablePtr&& other) noexcept { + ptr_ = std::move(other.ptr_); + } + + ~ClonablePtr() = default; + + public: + pointer release() noexcept { return ptr_.release(); } + void reset(pointer p = pointer()) noexcept { ptr_.reset(p); } + void swap(ClonablePtr& other) noexcept { ptr_.swap(other.ptr_); } + + public: + pointer get() const noexcept { return ptr_.get(); } + + operator bool() const noexcept { return ptr_; } + + element_type& operator*() const noexcept { return *ptr_; } + pointer operator->() const noexcept { return ptr_.get(); } + + private: + std::unique_ptr ptr_; +}; + +template +void swap(ClonablePtr& left, ClonablePtr& right) noexcept { + left.swap(right); +} + +template +bool operator==(const ClonablePtr& left, const ClonablePtr& right) { + return left.get() == right.get(); +} + +template +bool operator!=(const ClonablePtr& left, const ClonablePtr& right) { + return left.get() != right.get(); +} + +template +bool operator<(const ClonablePtr& left, const ClonablePtr& right) { + return left.get() < right.get(); +} + +template +bool operator<=(const ClonablePtr& left, const ClonablePtr& right) { + return left.get() <= right.get(); +} + +template +bool operator>(const ClonablePtr& left, const ClonablePtr& right) { + return left.get() > right.get(); +} + +template +bool operator>=(const ClonablePtr& left, const ClonablePtr& right) { + return left.get() >= right.get(); +} + +template +bool operator==(const ClonablePtr& left, std::nullptr_t) { + return left.get() == nullptr; +} + +template +bool operator!=(const ClonablePtr& left, std::nullptr_t) { + return left.get() != nullptr; +} + +template +bool operator<(const ClonablePtr& left, std::nullptr_t) { + return left.get() < nullptr; +} + +template +bool operator<=(const ClonablePtr& left, std::nullptr_t) { + return left.get() <= nullptr; +} + +template +bool operator>(const ClonablePtr& left, std::nullptr_t) { + return left.get() > nullptr; +} + +template +bool operator>=(const ClonablePtr& left, std::nullptr_t) { + return left.get() >= nullptr; +} + +template +bool operator==(std::nullptr_t, const ClonablePtr& right) { + return nullptr == right.get(); +} + +template +bool operator!=(std::nullptr_t, const ClonablePtr& right) { + return nullptr != right.get(); +} + +template +bool operator<(std::nullptr_t, const ClonablePtr& right) { + return nullptr < right.get(); +} + +template +bool operator<=(std::nullptr_t, const ClonablePtr& right) { + return nullptr <= right.get(); +} + +template +bool operator>(std::nullptr_t, const ClonablePtr& right) { + return nullptr > right.get(); +} + +template +bool operator>=(std::nullptr_t, const ClonablePtr& right) { + return nullptr >= right.get(); +} + +} // namespace cru + +namespace std { +template +struct hash> { + std::size_t operator()(const cru::ClonablePtr& p) const { + return std::hash::pointer>(p.get()); + } +}; +} // namespace std diff --git a/include/cru/ui/style/Condition.hpp b/include/cru/ui/style/Condition.hpp index c4fd2106..13ab7764 100644 --- a/include/cru/ui/style/Condition.hpp +++ b/include/cru/ui/style/Condition.hpp @@ -1,6 +1,7 @@ #pragma once #include "../Base.hpp" #include "cru/common/Base.hpp" +#include "cru/common/ClonablePtr.hpp" #include "cru/common/Event.hpp" #include "cru/ui/controls/IClickableControl.hpp" #include "cru/ui/helper/ClickDetector.hpp" @@ -17,25 +18,17 @@ class Condition : public Object { controls::Control* control) const = 0; virtual bool Judge(controls::Control* control) const = 0; - virtual std::unique_ptr Clone() const = 0; + virtual Condition* Clone() const = 0; }; class CompoundCondition : public Condition { public: - explicit CompoundCondition( - std::vector> conditions); - - const std::vector& GetConditions() const { - return readonly_conditions_; - } - - std::vector> CloneConditions() const; + explicit CompoundCondition(std::vector> conditions); std::vector ChangeOn(controls::Control* control) const override; - private: - std::vector> conditions_; - std::vector readonly_conditions_; + protected: + std::vector> conditions_; }; class AndCondition : public CompoundCondition { @@ -44,9 +37,7 @@ class AndCondition : public CompoundCondition { bool Judge(controls::Control* control) const override; - std::unique_ptr Clone() const override { - return std::make_unique(CloneConditions()); - } + AndCondition* Clone() const override { return new AndCondition(conditions_); } }; class OrCondition : public CompoundCondition { @@ -55,9 +46,7 @@ class OrCondition : public CompoundCondition { bool Judge(controls::Control* control) const override; - std::unique_ptr Clone() const override { - return std::make_unique(CloneConditions()); - } + OrCondition* Clone() const override { return new OrCondition(conditions_); } }; class FocusCondition : public Condition { @@ -67,8 +56,8 @@ class FocusCondition : public Condition { std::vector ChangeOn(controls::Control* control) const override; bool Judge(controls::Control* control) const override; - std::unique_ptr Clone() const override { - return std::make_unique(has_focus_); + FocusCondition* Clone() const override { + return new FocusCondition(has_focus_); } private: @@ -82,8 +71,8 @@ class ClickStateCondition : public Condition { std::vector ChangeOn(controls::Control* control) const override; bool Judge(controls::Control* control) const override; - std::unique_ptr Clone() const override { - return std::make_unique(click_state_); + ClickStateCondition* Clone() const override { + return new ClickStateCondition(click_state_); } private: diff --git a/include/cru/ui/style/StyleRule.hpp b/include/cru/ui/style/StyleRule.hpp index f1283e24..8ac42cd0 100644 --- a/include/cru/ui/style/StyleRule.hpp +++ b/include/cru/ui/style/StyleRule.hpp @@ -2,6 +2,7 @@ #include "Condition.hpp" #include "Styler.hpp" #include "cru/common/Base.hpp" +#include "cru/common/ClonablePtr.hpp" #include "cru/ui/Base.hpp" #include @@ -11,23 +12,10 @@ namespace cru::ui::style { class StyleRule : public Object { public: - StyleRule(std::unique_ptr condition, - std::unique_ptr styler, std::u16string name = {}); - - StyleRule(const StyleRule& other) - : condition_(other.condition_->Clone()), - styler_(other.styler_->Clone()), - name_(other.name_) {} - - StyleRule& operator=(const StyleRule& other) { - if (this != &other) { - condition_ = other.condition_->Clone(); - styler_ = other.styler_->Clone(); - name_ = other.name_; - } - return *this; - } + StyleRule(ClonablePtr condition, ClonablePtr styler, + std::u16string name = {}); + CRU_DEFAULT_COPY(StyleRule) CRU_DEFAULT_MOVE(StyleRule) ~StyleRule() override = default; @@ -37,21 +25,21 @@ class StyleRule : public Object { Condition* GetCondition() const { return condition_.get(); } Styler* GetStyler() const { return styler_.get(); } - StyleRule WithNewCondition(std::unique_ptr condition, + StyleRule WithNewCondition(ClonablePtr condition, std::u16string name = {}) const { - return StyleRule{std::move(condition), styler_->Clone(), std::move(name)}; + return StyleRule{std::move(condition), styler_, std::move(name)}; } - StyleRule WithNewStyler(std::unique_ptr styler, + StyleRule WithNewStyler(ClonablePtr styler, std::u16string name = {}) const { - return StyleRule{condition_->Clone(), std::move(styler), std::move(name)}; + return StyleRule{condition_, std::move(styler), std::move(name)}; } bool CheckAndApply(controls::Control* control) const; private: - std::unique_ptr condition_; - std::unique_ptr styler_; + ClonablePtr condition_; + ClonablePtr styler_; std::u16string name_; }; } // namespace cru::ui::style diff --git a/include/cru/ui/style/StyleRuleSet.hpp b/include/cru/ui/style/StyleRuleSet.hpp new file mode 100644 index 00000000..e69de29b diff --git a/include/cru/ui/style/Styler.hpp b/include/cru/ui/style/Styler.hpp index 4f4b18ba..2aece114 100644 --- a/include/cru/ui/style/Styler.hpp +++ b/include/cru/ui/style/Styler.hpp @@ -10,7 +10,7 @@ class Styler : public Object { public: virtual void Apply(controls::Control* control) const; - virtual std::unique_ptr Clone() const = 0; + virtual Styler* Clone() const = 0; }; class BorderStyler : public Styler { @@ -19,9 +19,7 @@ class BorderStyler : public Styler { void Apply(controls::Control* control) const override; - std::unique_ptr Clone() const override { - return std::make_unique(style_); - } + BorderStyler* Clone() const override { return new BorderStyler(style_); } private: ApplyBorderStyleInfo style_; diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index e61ed7de..4964a1ae 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -37,6 +37,7 @@ add_library(cru_ui STATIC style/Condition.cpp style/Styler.cpp style/StyleRule.cpp + style/StyleRuleSet.cpp ) target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/Base.hpp @@ -79,5 +80,6 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/style/Condition.hpp ${CRU_UI_INCLUDE_DIR}/style/Styler.hpp ${CRU_UI_INCLUDE_DIR}/style/StyleRule.hpp + ${CRU_UI_INCLUDE_DIR}/style/StyleRuleSet.hpp ) target_link_libraries(cru_ui PUBLIC cru_platform_gui) diff --git a/src/ui/style/Condition.cpp b/src/ui/style/Condition.cpp index 2b51bde3..891eb062 100644 --- a/src/ui/style/Condition.cpp +++ b/src/ui/style/Condition.cpp @@ -1,6 +1,7 @@ #include "cru/ui/style/Condition.hpp" #include +#include "cru/common/ClonablePtr.hpp" #include "cru/common/Event.hpp" #include "cru/ui/controls/Control.hpp" #include "cru/ui/controls/IClickableControl.hpp" @@ -8,16 +9,14 @@ namespace cru::ui::style { CompoundCondition::CompoundCondition( - std::vector> conditions) - : conditions_(std::move(conditions)) { - for (const auto& p : conditions_) readonly_conditions_.push_back(p.get()); -} + std::vector> conditions) + : conditions_(std::move(conditions)) {} std::vector CompoundCondition::ChangeOn( controls::Control* control) const { std::vector result; - for (auto condition : GetConditions()) { + for (auto condition : conditions_) { for (auto e : condition->ChangeOn(control)) { result.push_back(e); } @@ -26,24 +25,15 @@ std::vector CompoundCondition::ChangeOn( return result; } -std::vector> CompoundCondition::CloneConditions() - const { - std::vector> result; - for (auto condition : GetConditions()) { - result.push_back(condition->Clone()); - } - return result; -} - bool AndCondition::Judge(controls::Control* control) const { - for (auto condition : GetConditions()) { + for (auto condition : conditions_) { if (!condition->Judge(control)) return false; } return true; } bool OrCondition::Judge(controls::Control* control) const { - for (auto condition : GetConditions()) { + for (auto condition : conditions_) { if (condition->Judge(control)) return true; } return false; diff --git a/src/ui/style/StyleRule.cpp b/src/ui/style/StyleRule.cpp index 4a5ecf7e..1a72a970 100644 --- a/src/ui/style/StyleRule.cpp +++ b/src/ui/style/StyleRule.cpp @@ -1,6 +1,12 @@ #include "cru/ui/style/StyleRule.hpp" namespace cru::ui::style { +StyleRule::StyleRule(ClonablePtr condition, + ClonablePtr styler, std::u16string name) + : condition_(std::move(condition)), + styler_(std::move(styler)), + name_(std::move(name)) {} + bool StyleRule::CheckAndApply(controls::Control *control) const { auto active = condition_->Judge(control); if (active) { diff --git a/src/ui/style/StyleRuleSet.cpp b/src/ui/style/StyleRuleSet.cpp new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3 From b29fb11be2f043a3438a50d8942b4ad7d2af0034 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 3 Dec 2020 22:44:57 +0800 Subject: ... --- include/cru/common/ClonablePtr.hpp | 7 ++- include/cru/common/Event.hpp | 4 +- include/cru/ui/Base.hpp | 12 ++-- include/cru/ui/UiManager.hpp | 5 +- include/cru/ui/controls/Base.hpp | 22 +------ include/cru/ui/controls/Button.hpp | 5 -- include/cru/ui/controls/Control.hpp | 5 ++ include/cru/ui/controls/TextBox.hpp | 14 +---- include/cru/ui/helper/ClickDetector.hpp | 2 +- include/cru/ui/render/BorderRenderObject.hpp | 2 - include/cru/ui/style/Condition.hpp | 42 +++++++++++++ include/cru/ui/style/StyleRuleSet.hpp | 55 +++++++++++++++++ include/cru/ui/style/Styler.hpp | 7 ++- src/ui/UiManager.cpp | 91 ++++++++++++++++------------ src/ui/controls/Button.cpp | 19 +----- src/ui/controls/Control.cpp | 7 +++ src/ui/controls/TextBox.cpp | 29 +-------- src/ui/render/BorderRenderObject.cpp | 9 --- src/ui/style/Condition.cpp | 10 +++ src/ui/style/StyleRuleSet.cpp | 58 ++++++++++++++++++ src/win/gui/Window.cpp | 2 +- 21 files changed, 262 insertions(+), 145 deletions(-) (limited to 'include/cru/common') diff --git a/include/cru/common/ClonablePtr.hpp b/include/cru/common/ClonablePtr.hpp index 47a1d3bd..5e4b80c9 100644 --- a/include/cru/common/ClonablePtr.hpp +++ b/include/cru/common/ClonablePtr.hpp @@ -8,13 +8,16 @@ namespace cru { template class ClonablePtr { + template + friend class ClonablePtr; + public: using element_type = typename std::unique_ptr::element_type; using pointer = typename std::unique_ptr::pointer; ClonablePtr() = default; ClonablePtr(std::nullptr_t) noexcept : ptr_(nullptr) {} - ClonablePtr(pointer p) noexcept : ptr_(p) {} + explicit ClonablePtr(pointer p) noexcept : ptr_(p) {} ClonablePtr(std::unique_ptr&& p) noexcept : ptr_(std::move(p)) {} template (other.ptr->Clone()); + ptr_ = std::unique_ptr(other.ptr_->Clone()); } return *this; } diff --git a/include/cru/common/Event.hpp b/include/cru/common/Event.hpp index 59502527..7f7b4dd4 100644 --- a/include/cru/common/Event.hpp +++ b/include/cru/common/Event.hpp @@ -98,7 +98,7 @@ struct IBaseEvent { using SpyOnlyHandler = std::function; public: - virtual EventRevoker AddHandler(SpyOnlyHandler handler) = 0; + virtual EventRevoker AddSpyOnlyHandler(SpyOnlyHandler handler) = 0; }; // Provides an interface of event. @@ -147,7 +147,7 @@ class Event : public details::EventBase, public IEvent { CRU_DEFAULT_MOVE(Event) ~Event() = default; - EventRevoker AddHandler(SpyOnlyHandler handler) override { + EventRevoker AddSpyOnlyHandler(SpyOnlyHandler handler) override { const auto token = current_token_++; this->handler_data_list_.emplace_back(token, std::move(handler)); return CreateRevoker(token); diff --git a/include/cru/ui/Base.hpp b/include/cru/ui/Base.hpp index 8595258d..57beb723 100644 --- a/include/cru/ui/Base.hpp +++ b/include/cru/ui/Base.hpp @@ -40,6 +40,10 @@ namespace render { class RenderObject; } +namespace style { +class StyleRuleSet; +} + //-------------------- region: basic types -------------------- namespace internal { constexpr int align_start = 0; @@ -87,14 +91,6 @@ inline bool operator!=(const CornerRadius& left, const CornerRadius& right) { return !(left == right); } -struct BorderStyle { - std::shared_ptr border_brush; - Thickness border_thickness; - CornerRadius border_radius; - std::shared_ptr foreground_brush; - std::shared_ptr background_brush; -}; - class CanvasPaintEventArgs { public: CanvasPaintEventArgs(platform::graphics::IPainter* painter, diff --git a/include/cru/ui/UiManager.hpp b/include/cru/ui/UiManager.hpp index 64599d99..e747fcd2 100644 --- a/include/cru/ui/UiManager.hpp +++ b/include/cru/ui/UiManager.hpp @@ -2,6 +2,7 @@ #include "Base.hpp" #include "controls/Base.hpp" +#include "style/StyleRuleSet.hpp" #include #include @@ -13,8 +14,8 @@ struct ThemeResources { std::shared_ptr text_brush; std::shared_ptr text_selection_brush; std::shared_ptr caret_brush; - controls::ButtonStyle button_style; - controls::TextBoxBorderStyle text_box_border_style; + style::StyleRuleSet button_style; + style::StyleRuleSet text_box_style; }; class UiManager : public Object { diff --git a/include/cru/ui/controls/Base.hpp b/include/cru/ui/controls/Base.hpp index 82c31d1e..7c85cdb2 100644 --- a/include/cru/ui/controls/Base.hpp +++ b/include/cru/ui/controls/Base.hpp @@ -1,24 +1,4 @@ #pragma once #include "../Base.hpp" -namespace cru::ui::controls { -using ButtonStateStyle = ui::BorderStyle; - -struct ButtonStyle { - // corresponds to ClickState::None - ButtonStateStyle normal; - // corresponds to ClickState::Hover - ButtonStateStyle hover; - // corresponds to ClickState::Press - ButtonStateStyle press; - // corresponds to ClickState::PressInactive - ButtonStateStyle press_cancel; -}; - -struct TextBoxBorderStyle { - ui::BorderStyle normal; - ui::BorderStyle hover; - ui::BorderStyle focus; - ui::BorderStyle focus_hover; -}; -} // namespace cru::ui::controls +namespace cru::ui::controls {} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/Button.hpp b/include/cru/ui/controls/Button.hpp index 7299c146..1c9b1216 100644 --- a/include/cru/ui/controls/Button.hpp +++ b/include/cru/ui/controls/Button.hpp @@ -41,14 +41,9 @@ class Button : public ContentControl, void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) override; - const ButtonStyle& GetStyle() const { return style_; } - void SetStyle(ButtonStyle style); - private: std::unique_ptr render_object_{}; - ButtonStyle style_; - helper::ClickDetector click_detector_; }; } // namespace cru::ui::controls diff --git a/include/cru/ui/controls/Control.hpp b/include/cru/ui/controls/Control.hpp index 96aad2bd..0d34bc63 100644 --- a/include/cru/ui/controls/Control.hpp +++ b/include/cru/ui/controls/Control.hpp @@ -66,6 +66,9 @@ class Control : public Object { // null to unset void SetCursor(std::shared_ptr cursor); + public: + style::StyleRuleSet* GetStyleRuleSet(); + //*************** region: events *************** public: // Raised when mouse enter the control. Even when the control itself captures @@ -147,5 +150,7 @@ class Control : public Object { bool is_mouse_over_ = false; std::shared_ptr cursor_ = nullptr; + + std::unique_ptr style_rule_set_; }; } // namespace cru::ui::controls diff --git a/include/cru/ui/controls/TextBox.hpp b/include/cru/ui/controls/TextBox.hpp index 91d38c61..75e7cb65 100644 --- a/include/cru/ui/controls/TextBox.hpp +++ b/include/cru/ui/controls/TextBox.hpp @@ -1,5 +1,6 @@ #pragma once #include "NoChildControl.hpp" +#include "IBorderControl.hpp" #include @@ -7,7 +8,7 @@ namespace cru::ui::controls { template class TextControlService; -class TextBox : public NoChildControl { +class TextBox : public NoChildControl, public IBorderControl { public: static constexpr std::u16string_view control_type = u"TextBox"; @@ -29,22 +30,13 @@ class TextBox : public NoChildControl { gsl::not_null GetTextRenderObject(); render::ScrollRenderObject* GetScrollRenderObject(); - const TextBoxBorderStyle& GetBorderStyle(); - void SetBorderStyle(TextBoxBorderStyle border_style); - - protected: - void OnMouseHoverChange(bool newHover) override; - - private: - void UpdateBorderStyle(); + void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) override; private: std::unique_ptr border_render_object_; std::unique_ptr scroll_render_object_; std::unique_ptr text_render_object_; - TextBoxBorderStyle border_style_; - std::unique_ptr> service_; }; } // namespace cru::ui::controls diff --git a/include/cru/ui/helper/ClickDetector.hpp b/include/cru/ui/helper/ClickDetector.hpp index 0df77c60..b58297b1 100644 --- a/include/cru/ui/helper/ClickDetector.hpp +++ b/include/cru/ui/helper/ClickDetector.hpp @@ -71,7 +71,7 @@ class ClickDetector : public Object { private: controls::Control* control_; - ClickState state_; + ClickState state_ = ClickState::None; bool enable_ = true; MouseButton trigger_button_ = mouse_buttons::left | mouse_buttons::right; diff --git a/include/cru/ui/render/BorderRenderObject.hpp b/include/cru/ui/render/BorderRenderObject.hpp index ec0bd52b..3d4f4dad 100644 --- a/include/cru/ui/render/BorderRenderObject.hpp +++ b/include/cru/ui/render/BorderRenderObject.hpp @@ -64,8 +64,6 @@ class BorderRenderObject : public RenderObject { InvalidatePaint(); } - void SetBorderStyle(const BorderStyle& style); - void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style); RenderObject* HitTest(const Point& point) override; diff --git a/include/cru/ui/style/Condition.hpp b/include/cru/ui/style/Condition.hpp index 13ab7764..d5cf16f2 100644 --- a/include/cru/ui/style/Condition.hpp +++ b/include/cru/ui/style/Condition.hpp @@ -21,6 +21,21 @@ class Condition : public Object { virtual Condition* Clone() const = 0; }; +class NoCondition : public Condition { + public: + static ClonablePtr Create() { + return ClonablePtr(new NoCondition); + }; + + std::vector ChangeOn(controls::Control*) const override { + return {}; + } + + bool Judge(controls::Control*) const override { return true; } + + NoCondition* Clone() const override { return new NoCondition; } +}; + class CompoundCondition : public Condition { public: explicit CompoundCondition(std::vector> conditions); @@ -51,6 +66,10 @@ class OrCondition : public CompoundCondition { class FocusCondition : public Condition { public: + static ClonablePtr Create(bool has_focus) { + return ClonablePtr(new FocusCondition(has_focus)); + } + explicit FocusCondition(bool has_focus); std::vector ChangeOn(controls::Control* control) const override; @@ -64,8 +83,31 @@ class FocusCondition : public Condition { bool has_focus_; }; +class HoverCondition : public Condition { + public: + static ClonablePtr Create(bool hover) { + return ClonablePtr(new HoverCondition(hover)); + } + + explicit HoverCondition(bool hover) : hover_(hover) {} + + std::vector ChangeOn(controls::Control* control) const override; + bool Judge(controls::Control* control) const override; + + HoverCondition* Clone() const override { return new HoverCondition(hover_); } + + private: + bool hover_; +}; + class ClickStateCondition : public Condition { public: + static ClonablePtr Create( + helper::ClickState click_state) { + return ClonablePtr( + new ClickStateCondition(click_state)); + } + explicit ClickStateCondition(helper::ClickState click_state); std::vector ChangeOn(controls::Control* control) const override; diff --git a/include/cru/ui/style/StyleRuleSet.hpp b/include/cru/ui/style/StyleRuleSet.hpp index e69de29b..3ec71730 100644 --- a/include/cru/ui/style/StyleRuleSet.hpp +++ b/include/cru/ui/style/StyleRuleSet.hpp @@ -0,0 +1,55 @@ +#pragma once +#include "StyleRule.hpp" +#include "cru/common/Base.hpp" +#include "cru/common/Event.hpp" +#include "gsl/gsl_assert" + +namespace cru::ui::style { +class StyleRuleSet : public Object { + public: + StyleRuleSet() : control_(nullptr) {} + explicit StyleRuleSet(controls::Control* control) : control_(control) {} + + CRU_DELETE_COPY(StyleRuleSet) + CRU_DELETE_MOVE(StyleRuleSet) + + ~StyleRuleSet() override = default; + + public: + gsl::index GetSize() const { return static_cast(rules_.size()); } + const std::vector& GetRules() const { return rules_; } + + void AddStyleRule(StyleRule rule) { + AddStyleRule(std::move(rule), GetSize()); + } + + void AddStyleRule(StyleRule rule, gsl::index index); + + template + void AddStyleRuleRange(Iter start, Iter end, gsl::index index) { + Expects(index >= 0 && index <= GetSize()); + rules_.insert(rules_.cbegin() + index, std::move(start), std::move(end)); + UpdateChangeListener(); + UpdateStyle(); + } + + void RemoveStyleRule(gsl::index index, gsl::index count = 1); + + void Clear() { RemoveStyleRule(0, GetSize()); } + + void Set(const StyleRuleSet& other); + + const StyleRule& operator[](gsl::index index) const { return rules_[index]; } + + private: + void UpdateChangeListener(); + void UpdateStyle(); + + private: + controls::Control* control_; + + std::vector rules_; + + EventRevokerListGuard guard_; +}; +} // namespace cru::ui::style diff --git a/include/cru/ui/style/Styler.hpp b/include/cru/ui/style/Styler.hpp index 2aece114..10b169b1 100644 --- a/include/cru/ui/style/Styler.hpp +++ b/include/cru/ui/style/Styler.hpp @@ -2,19 +2,24 @@ #include "../Base.hpp" #include "ApplyBorderStyleInfo.hpp" #include "cru/common/Base.hpp" +#include "cru/common/ClonablePtr.hpp" #include namespace cru::ui::style { class Styler : public Object { public: - virtual void Apply(controls::Control* control) const; + virtual void Apply(controls::Control* control) const = 0; virtual Styler* Clone() const = 0; }; class BorderStyler : public Styler { public: + static ClonablePtr Create(ApplyBorderStyleInfo style) { + return ClonablePtr(new BorderStyler(std::move(style))); + } + explicit BorderStyler(ApplyBorderStyleInfo style); void Apply(controls::Control* control) const override; diff --git a/src/ui/UiManager.cpp b/src/ui/UiManager.cpp index 62995f86..bb7f5841 100644 --- a/src/ui/UiManager.cpp +++ b/src/ui/UiManager.cpp @@ -5,9 +5,14 @@ #include "cru/platform/graphics/Factory.hpp" #include "cru/platform/graphics/Font.hpp" #include "cru/platform/gui/UiApplication.hpp" +#include "cru/ui/style/ApplyBorderStyleInfo.hpp" +#include "cru/ui/style/Condition.hpp" +#include "cru/ui/style/Styler.hpp" namespace cru::ui { using namespace cru::platform::graphics; +using namespace cru::ui::style; +using namespace cru::ui::helper; namespace { std::unique_ptr CreateSolidColorBrush(IGraphFactory* factory, @@ -35,49 +40,59 @@ UiManager::UiManager() { theme_resource_.default_font = factory->CreateFont(theme_resource_.default_font_family, 24.0f); - const auto black_brush = std::shared_ptr( - CreateSolidColorBrush(factory, colors::black)); + const auto black_brush = + std::shared_ptr( + CreateSolidColorBrush(factory, colors::black)); theme_resource_.text_brush = black_brush; theme_resource_.text_selection_brush = CreateSolidColorBrush(factory, colors::skyblue); theme_resource_.caret_brush = black_brush; - theme_resource_.button_style.normal.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x00bfff)); - theme_resource_.button_style.hover.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x47d1ff)); - theme_resource_.button_style.press.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x91e4ff)); - theme_resource_.button_style.press_cancel.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x91e4ff)); - - theme_resource_.button_style.normal.border_thickness = - theme_resource_.button_style.hover.border_thickness = - theme_resource_.button_style.press.border_thickness = - theme_resource_.button_style.press_cancel.border_thickness = - Thickness(3); - - theme_resource_.button_style.normal.border_radius = - theme_resource_.button_style.hover.border_radius = - theme_resource_.button_style.press.border_radius = - theme_resource_.button_style.press_cancel.border_radius = - CornerRadius({5, 5}); - - theme_resource_.text_box_border_style.normal.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0xced4da)); - theme_resource_.text_box_border_style.normal.border_radius = CornerRadius(5); - theme_resource_.text_box_border_style.normal.border_thickness = Thickness(1); - - theme_resource_.text_box_border_style.hover = - theme_resource_.text_box_border_style.normal; - - theme_resource_.text_box_border_style.focus.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x495057)); - theme_resource_.text_box_border_style.focus.border_radius = CornerRadius(5); - theme_resource_.text_box_border_style.focus.border_thickness = Thickness(1); - - theme_resource_.text_box_border_style.focus_hover = - theme_resource_.text_box_border_style.focus; + theme_resource_.button_style.AddStyleRule( + {ClickStateCondition::Create(ClickState::None), + BorderStyler::Create(ApplyBorderStyleInfo{ + CreateSolidColorBrush(factory, Color::FromHex(0x00bfff)), + Thickness(3), CornerRadius(5), nullptr, nullptr}), + u"DefaultButtonNormal"}); + theme_resource_.button_style.AddStyleRule( + {ClickStateCondition::Create(ClickState::Hover), + BorderStyler::Create(ApplyBorderStyleInfo{ + CreateSolidColorBrush(factory, Color::FromHex(0x47d1ff)), + Thickness(3), CornerRadius(5), nullptr, nullptr}), + u"DefaultButtonHover"}); + theme_resource_.button_style.AddStyleRule( + {ClickStateCondition::Create(ClickState::Press), + BorderStyler::Create(ApplyBorderStyleInfo{ + CreateSolidColorBrush(factory, Color::FromHex(0x91e4ff)), + Thickness(3), CornerRadius(5), nullptr, nullptr}), + u"DefaultButtonPress"}); + theme_resource_.button_style.AddStyleRule( + {ClickStateCondition::Create(ClickState::PressInactive), + BorderStyler::Create(ApplyBorderStyleInfo{ + CreateSolidColorBrush(factory, Color::FromHex(0x91e4ff)), + Thickness(3), CornerRadius(5), nullptr, nullptr}), + u"DefaultButtonPressInactive"}); + + theme_resource_.text_box_style.AddStyleRule( + {HoverCondition::Create(false), + BorderStyler::Create(ApplyBorderStyleInfo{ + CreateSolidColorBrush(factory, Color::FromHex(0xced4da)), + Thickness(1), CornerRadius(5), nullptr, nullptr}), + u"DefaultTextBoxNormal"}); + + theme_resource_.text_box_style.AddStyleRule( + {HoverCondition::Create(true), + BorderStyler::Create(ApplyBorderStyleInfo{ + CreateSolidColorBrush(factory, Color::FromHex(0xced4da)), + Thickness(1), CornerRadius(5), nullptr, nullptr}), + u"DefaultTextBoxHover"}); + + theme_resource_.text_box_style.AddStyleRule( + {FocusCondition::Create(true), + BorderStyler::Create(ApplyBorderStyleInfo{ + CreateSolidColorBrush(factory, Color::FromHex(0x495057)), + Thickness(1), CornerRadius(5), nullptr, nullptr}), + u"DefaultTextBoxHover"}); } UiManager::~UiManager() = default; diff --git a/src/ui/controls/Button.cpp b/src/ui/controls/Button.cpp index 6f19e6b9..7858eadb 100644 --- a/src/ui/controls/Button.cpp +++ b/src/ui/controls/Button.cpp @@ -13,50 +13,37 @@ namespace cru::ui::controls { using cru::platform::gui::SystemCursorType; namespace { -void Set(render::BorderRenderObject* o, const ButtonStateStyle& s) { - o->SetBorderBrush(s.border_brush); - o->SetBorderThickness(s.border_thickness); - o->SetBorderRadius(s.border_radius); - o->SetForegroundBrush(s.foreground_brush); - o->SetBackgroundBrush(s.background_brush); -} - std::shared_ptr GetSystemCursor(SystemCursorType type) { return GetUiApplication()->GetCursorManager()->GetSystemCursor(type); } } // namespace Button::Button() : click_detector_(this) { - style_ = UiManager::GetInstance()->GetThemeResources()->button_style; - render_object_ = std::make_unique(); render_object_->SetAttachedControl(this); SetContainerRenderObject(render_object_.get()); - - Set(render_object_.get(), style_.normal); render_object_->SetBorderEnabled(true); click_detector_.StateChangeEvent()->AddHandler( [this](const helper::ClickState& state) { switch (state) { case helper::ClickState::None: - Set(render_object_.get(), style_.normal); SetCursor(GetSystemCursor(SystemCursorType::Arrow)); break; case helper::ClickState::Hover: - Set(render_object_.get(), style_.hover); SetCursor(GetSystemCursor(SystemCursorType::Hand)); break; case helper::ClickState::Press: - Set(render_object_.get(), style_.press); SetCursor(GetSystemCursor(SystemCursorType::Hand)); break; case helper::ClickState::PressInactive: - Set(render_object_.get(), style_.press_cancel); SetCursor(GetSystemCursor(SystemCursorType::Arrow)); break; } }); + + GetStyleRuleSet()->Set( + UiManager::GetInstance()->GetThemeResources()->button_style); } Button::~Button() = default; diff --git a/src/ui/controls/Control.cpp b/src/ui/controls/Control.cpp index c1316a62..1c4ffe51 100644 --- a/src/ui/controls/Control.cpp +++ b/src/ui/controls/Control.cpp @@ -6,6 +6,7 @@ #include "cru/ui/Base.hpp" #include "cru/ui/host/WindowHost.hpp" #include "cru/ui/render/RenderObject.hpp" +#include "cru/ui/style/StyleRuleSet.hpp" #include @@ -15,6 +16,8 @@ using platform::gui::IUiApplication; using platform::gui::SystemCursorType; Control::Control() { + style_rule_set_ = std::make_unique(this); + MouseEnterEvent()->Direct()->AddHandler([this](event::MouseEventArgs&) { this->is_mouse_over_ = true; this->OnMouseHoverChange(true); @@ -94,6 +97,10 @@ void Control::SetCursor(std::shared_ptr cursor) { } } +style::StyleRuleSet* Control::GetStyleRuleSet() { + return style_rule_set_.get(); +} + void Control::AddChild(Control* control, const Index position) { Expects(control->GetParent() == nullptr); // The control already has a parent. diff --git a/src/ui/controls/TextBox.cpp b/src/ui/controls/TextBox.cpp index 6ba6ecb2..031894c0 100644 --- a/src/ui/controls/TextBox.cpp +++ b/src/ui/controls/TextBox.cpp @@ -18,8 +18,6 @@ TextBox::TextBox() scroll_render_object_(new ScrollRenderObject()) { 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); @@ -38,17 +36,8 @@ TextBox::TextBox() service_->SetEditable(true); border_render_object_->SetBorderEnabled(true); - border_render_object_->SetBorderStyle(border_style_.normal); - - GainFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) { - this->service_->SetCaretVisible(true); - this->UpdateBorderStyle(); - }); - LoseFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) { - this->service_->SetCaretVisible(false); - this->UpdateBorderStyle(); - }); + GetStyleRuleSet()->Set(theme_resources->text_box_style); } TextBox::~TextBox() {} @@ -65,19 +54,7 @@ render::ScrollRenderObject* TextBox::GetScrollRenderObject() { return scroll_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)); +void TextBox::ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) { + border_render_object_->ApplyBorderStyle(style); } } // namespace cru::ui::controls diff --git a/src/ui/render/BorderRenderObject.cpp b/src/ui/render/BorderRenderObject.cpp index 5abc7832..c176e760 100644 --- a/src/ui/render/BorderRenderObject.cpp +++ b/src/ui/render/BorderRenderObject.cpp @@ -17,15 +17,6 @@ BorderRenderObject::BorderRenderObject() { BorderRenderObject::~BorderRenderObject() {} -void BorderRenderObject::SetBorderStyle(const BorderStyle& style) { - border_brush_ = style.border_brush; - border_thickness_ = style.border_thickness; - border_radius_ = style.border_radius; - foreground_brush_ = style.foreground_brush; - background_brush_ = style.background_brush; - InvalidateLayout(); -} - void BorderRenderObject::ApplyBorderStyle( const style::ApplyBorderStyleInfo& style) { if (style.border_brush != nullptr) border_brush_ = style.border_brush; diff --git a/src/ui/style/Condition.cpp b/src/ui/style/Condition.cpp index 891eb062..f4866c04 100644 --- a/src/ui/style/Condition.cpp +++ b/src/ui/style/Condition.cpp @@ -51,6 +51,16 @@ bool FocusCondition::Judge(controls::Control* control) const { return control->HasFocus() == has_focus_; } +std::vector HoverCondition::ChangeOn( + controls::Control* control) const { + return {control->MouseEnterEvent()->Direct(), + control->MouseLeaveEvent()->Direct()}; +} + +bool HoverCondition::Judge(controls::Control* control) const { + return control->IsMouseOver() == hover_; +} + ClickStateCondition::ClickStateCondition(helper::ClickState click_state) : click_state_(click_state) {} diff --git a/src/ui/style/StyleRuleSet.cpp b/src/ui/style/StyleRuleSet.cpp index e69de29b..403fe114 100644 --- a/src/ui/style/StyleRuleSet.cpp +++ b/src/ui/style/StyleRuleSet.cpp @@ -0,0 +1,58 @@ +#include "cru/ui/style/StyleRuleSet.hpp" +#include "cru/common/Event.hpp" +#include "gsl/gsl_assert" + +#include + +namespace cru::ui::style { +void StyleRuleSet::AddStyleRule(StyleRule rule, gsl::index index) { + Expects(index >= 0 && index <= GetSize()); + + rules_.insert(rules_.cbegin() + index, std::move(rule)); + + UpdateChangeListener(); + UpdateStyle(); +} + +void StyleRuleSet::RemoveStyleRule(gsl::index index, gsl::index count) { + Expects(index >= 0); + Expects(count >= 0 && index + count <= GetSize()); + + rules_.erase(rules_.cbegin() + index, rules_.cbegin() + index + count); + + UpdateChangeListener(); + UpdateStyle(); +} + +void StyleRuleSet::Set(const StyleRuleSet& other) { + rules_ = other.rules_; + UpdateChangeListener(); + UpdateStyle(); +} + +void StyleRuleSet::UpdateChangeListener() { + if (control_ == nullptr) return; + + guard_.Clear(); + + std::unordered_set events; + for (const auto& rule : rules_) { + auto e = rule.GetCondition()->ChangeOn(control_); + events.insert(e.cbegin(), e.cend()); + } + + for (auto e : events) { + guard_ += e->AddSpyOnlyHandler([this] { this->UpdateStyle(); }); + } +} + +void StyleRuleSet::UpdateStyle() { + if (control_ == nullptr) return; + + for (const auto& rule : rules_) { + if (rule.GetCondition()->Judge(control_)) { + rule.GetStyler()->Apply(control_); + } + } +} +} // namespace cru::ui::style diff --git a/src/win/gui/Window.cpp b/src/win/gui/Window.cpp index 174b8931..efd3bfcc 100644 --- a/src/win/gui/Window.cpp +++ b/src/win/gui/Window.cpp @@ -367,9 +367,9 @@ RECT WinNativeWindow::GetClientRectPixel() { } void WinNativeWindow::OnDestroyInternal() { + destroy_event_.Raise(nullptr); application_->GetWindowManager()->UnregisterWindow(hwnd_); hwnd_ = nullptr; - destroy_event_.Raise(nullptr); if (!sync_flag_) { sync_flag_ = true; delete this; -- cgit v1.2.3 From 9fcebe16b5ad4acc8b2e206dbe163e58b1f75cdf Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 9 Dec 2020 17:21:44 +0800 Subject: ... --- include/cru/common/Event.hpp | 2 + include/cru/ui/Base.hpp | 1 + include/cru/ui/controls/Control.hpp | 1 + include/cru/ui/style/StyleRuleSet.hpp | 40 +++++++++++++++++-- src/ui/controls/Button.cpp | 4 +- src/ui/controls/Control.cpp | 4 +- src/ui/controls/TextBox.cpp | 2 +- src/ui/style/StyleRuleSet.cpp | 74 ++++++++++++++++++++++++++--------- 8 files changed, 102 insertions(+), 26 deletions(-) (limited to 'include/cru/common') diff --git a/include/cru/common/Event.hpp b/include/cru/common/Event.hpp index 7f7b4dd4..aa8fadbb 100644 --- a/include/cru/common/Event.hpp +++ b/include/cru/common/Event.hpp @@ -233,6 +233,8 @@ class EventRevokerGuard { EventRevoker Release() { return std::move(*revoker_.release()); } + void Reset() { revoker_.reset(); } + void Reset(EventRevoker&& revoker) { revoker_.reset(new EventRevoker(std::move(revoker))); } diff --git a/include/cru/ui/Base.hpp b/include/cru/ui/Base.hpp index 57beb723..b2939a0b 100644 --- a/include/cru/ui/Base.hpp +++ b/include/cru/ui/Base.hpp @@ -42,6 +42,7 @@ class RenderObject; namespace style { class StyleRuleSet; +class StyleRuleSetBind; } //-------------------- region: basic types -------------------- diff --git a/include/cru/ui/controls/Control.hpp b/include/cru/ui/controls/Control.hpp index 0d34bc63..341b6ef2 100644 --- a/include/cru/ui/controls/Control.hpp +++ b/include/cru/ui/controls/Control.hpp @@ -152,5 +152,6 @@ class Control : public Object { std::shared_ptr cursor_ = nullptr; std::unique_ptr style_rule_set_; + std::unique_ptr style_rule_set_bind_; }; } // namespace cru::ui::controls diff --git a/include/cru/ui/style/StyleRuleSet.hpp b/include/cru/ui/style/StyleRuleSet.hpp index 3ec71730..ba3f8b4c 100644 --- a/include/cru/ui/style/StyleRuleSet.hpp +++ b/include/cru/ui/style/StyleRuleSet.hpp @@ -2,13 +2,14 @@ #include "StyleRule.hpp" #include "cru/common/Base.hpp" #include "cru/common/Event.hpp" -#include "gsl/gsl_assert" + +#include namespace cru::ui::style { class StyleRuleSet : public Object { public: - StyleRuleSet() : control_(nullptr) {} - explicit StyleRuleSet(controls::Control* control) : control_(control) {} + StyleRuleSet() = default; + explicit StyleRuleSet(StyleRuleSet* parent); CRU_DELETE_COPY(StyleRuleSet) CRU_DELETE_MOVE(StyleRuleSet) @@ -16,6 +17,9 @@ class StyleRuleSet : public Object { ~StyleRuleSet() override = default; public: + StyleRuleSet* GetParent() const { return parent_; } + void SetParent(StyleRuleSet* parent); + gsl::index GetSize() const { return static_cast(rules_.size()); } const std::vector& GetRules() const { return rules_; } @@ -41,14 +45,42 @@ class StyleRuleSet : public Object { const StyleRule& operator[](gsl::index index) const { return rules_[index]; } + // Triggered whenever a change happened to this (rule add or remove, parent + // change ...). Subscribe to this and update style change listeners and style. + IEvent* ChangeEvent() { return &change_event_; } + + private: + void RaiseChangeEvent() { change_event_.Raise(nullptr); } + + private: + Event change_event_; + + StyleRuleSet* parent_ = nullptr; + EventRevokerGuard parent_change_event_guard_; + + std::vector rules_; +}; + +class StyleRuleSetBind { + public: + StyleRuleSetBind(controls::Control* control, StyleRuleSet* ruleset); + + CRU_DELETE_COPY(StyleRuleSetBind) + CRU_DELETE_MOVE(StyleRuleSetBind) + + ~StyleRuleSetBind() = default; + private: + void UpdateRuleSetChainCache(); void UpdateChangeListener(); void UpdateStyle(); private: controls::Control* control_; + StyleRuleSet* ruleset_; - std::vector rules_; + // child first, parent last. + std::vector ruleset_chain_cache_; EventRevokerListGuard guard_; }; diff --git a/src/ui/controls/Button.cpp b/src/ui/controls/Button.cpp index 7858eadb..8bd9f93f 100644 --- a/src/ui/controls/Button.cpp +++ b/src/ui/controls/Button.cpp @@ -42,8 +42,8 @@ Button::Button() : click_detector_(this) { } }); - GetStyleRuleSet()->Set( - UiManager::GetInstance()->GetThemeResources()->button_style); + GetStyleRuleSet()->SetParent( + &UiManager::GetInstance()->GetThemeResources()->button_style); } Button::~Button() = default; diff --git a/src/ui/controls/Control.cpp b/src/ui/controls/Control.cpp index 1c4ffe51..29c2c46a 100644 --- a/src/ui/controls/Control.cpp +++ b/src/ui/controls/Control.cpp @@ -16,7 +16,9 @@ using platform::gui::IUiApplication; using platform::gui::SystemCursorType; Control::Control() { - style_rule_set_ = std::make_unique(this); + style_rule_set_ = std::make_unique(); + style_rule_set_bind_ = + std::make_unique(this, style_rule_set_.get()); MouseEnterEvent()->Direct()->AddHandler([this](event::MouseEventArgs&) { this->is_mouse_over_ = true; diff --git a/src/ui/controls/TextBox.cpp b/src/ui/controls/TextBox.cpp index 0d14dbcf..d8317a8c 100644 --- a/src/ui/controls/TextBox.cpp +++ b/src/ui/controls/TextBox.cpp @@ -36,7 +36,7 @@ TextBox::TextBox() border_render_object_->SetBorderEnabled(true); - GetStyleRuleSet()->Set(theme_resources->text_box_style); + GetStyleRuleSet()->SetParent(&theme_resources->text_box_style); } TextBox::~TextBox() {} diff --git a/src/ui/style/StyleRuleSet.cpp b/src/ui/style/StyleRuleSet.cpp index 403fe114..24b88af9 100644 --- a/src/ui/style/StyleRuleSet.cpp +++ b/src/ui/style/StyleRuleSet.cpp @@ -1,17 +1,30 @@ #include "cru/ui/style/StyleRuleSet.hpp" #include "cru/common/Event.hpp" +#include "cru/ui/controls/Control.hpp" #include "gsl/gsl_assert" #include namespace cru::ui::style { +StyleRuleSet::StyleRuleSet(StyleRuleSet* parent) { SetParent(parent); } + +void StyleRuleSet::SetParent(StyleRuleSet* parent) { + if (parent == parent_) return; + parent_change_event_guard_.Reset(); + parent_ = parent; + if (parent != nullptr) { + parent_change_event_guard_.Reset(parent->ChangeEvent()->AddSpyOnlyHandler( + [this] { this->RaiseChangeEvent(); })); + } + RaiseChangeEvent(); +} + void StyleRuleSet::AddStyleRule(StyleRule rule, gsl::index index) { Expects(index >= 0 && index <= GetSize()); rules_.insert(rules_.cbegin() + index, std::move(rule)); - UpdateChangeListener(); - UpdateStyle(); + RaiseChangeEvent(); } void StyleRuleSet::RemoveStyleRule(gsl::index index, gsl::index count) { @@ -20,25 +33,48 @@ void StyleRuleSet::RemoveStyleRule(gsl::index index, gsl::index count) { rules_.erase(rules_.cbegin() + index, rules_.cbegin() + index + count); - UpdateChangeListener(); - UpdateStyle(); + RaiseChangeEvent(); } void StyleRuleSet::Set(const StyleRuleSet& other) { rules_ = other.rules_; - UpdateChangeListener(); - UpdateStyle(); + + RaiseChangeEvent(); +} + +StyleRuleSetBind::StyleRuleSetBind(controls::Control* control, + StyleRuleSet* ruleset) + : control_(control), ruleset_(ruleset) { + Expects(control); + Expects(ruleset); + + ruleset->ChangeEvent()->AddSpyOnlyHandler([this] { + UpdateRuleSetChainCache(); + UpdateChangeListener(); + UpdateStyle(); + }); } -void StyleRuleSet::UpdateChangeListener() { - if (control_ == nullptr) return; +void StyleRuleSetBind::UpdateRuleSetChainCache() { + ruleset_chain_cache_.clear(); + auto parent = ruleset_; + while (parent != nullptr) { + ruleset_chain_cache_.push_back(parent); + parent = parent->GetParent(); + } +} +void StyleRuleSetBind::UpdateChangeListener() { guard_.Clear(); std::unordered_set events; - for (const auto& rule : rules_) { - auto e = rule.GetCondition()->ChangeOn(control_); - events.insert(e.cbegin(), e.cend()); + + // ruleset order does not matter + for (auto ruleset : ruleset_chain_cache_) { + for (const auto& rule : ruleset->GetRules()) { + auto e = rule.GetCondition()->ChangeOn(control_); + events.insert(e.cbegin(), e.cend()); + } } for (auto e : events) { @@ -46,13 +82,15 @@ void StyleRuleSet::UpdateChangeListener() { } } -void StyleRuleSet::UpdateStyle() { - if (control_ == nullptr) return; - - for (const auto& rule : rules_) { - if (rule.GetCondition()->Judge(control_)) { - rule.GetStyler()->Apply(control_); - } +void StyleRuleSetBind::UpdateStyle() { + // cache is parent last, but when calculate style, parent first, so iterate + // reverse. + for (auto iter = ruleset_chain_cache_.crbegin(); + iter != ruleset_chain_cache_.crend(); ++iter) { + for (const auto& rule : (*iter)->GetRules()) + if (rule.GetCondition()->Judge(control_)) { + rule.GetStyler()->Apply(control_); + } } } } // namespace cru::ui::style -- cgit v1.2.3 From 6523ba8e0f2830f3cd4434d8331882077be3f82c Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 26 Dec 2020 23:41:02 +0800 Subject: ... --- include/cru/common/StringUtil.hpp | 19 ++++++++++- src/common/StringUtil.cpp | 55 ++++++++++++++++++++++++++++++ src/ui/controls/TextHostControlService.cpp | 16 +++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) (limited to 'include/cru/common') diff --git a/include/cru/common/StringUtil.hpp b/include/cru/common/StringUtil.hpp index 276048f5..62999d53 100644 --- a/include/cru/common/StringUtil.hpp +++ b/include/cru/common/StringUtil.hpp @@ -1,7 +1,10 @@ #pragma once -#include #include "Base.hpp" +#include +#include +#include + namespace cru { using CodePoint = std::int32_t; constexpr CodePoint k_invalid_code_point = -1; @@ -128,4 +131,18 @@ std::u16string ToUtf16(std::string_view s); // If given s is not a valid utf16 string, return value is UD. bool Utf16IsValidInsertPosition(std::u16string_view s, gsl::index position); + +// Return position after the character making predicate returns true or 0 if no +// character doing so. +gsl::index Utf16BackwardUntil(std::u16string_view str, gsl::index position, + const std::function& predicate); +// Return position before the character making predicate returns true or +// str.size() if no character doing so. +gsl::index Utf16ForwardUntil(std::u16string_view str, gsl::index position, + const std::function& predicate); + +gsl::index Utf16PreviousWord(std::u16string_view str, gsl::index position, + bool* is_space = nullptr); +gsl::index Utf16NextWord(std::u16string_view str, gsl::index position, + bool* is_space = nullptr); } // namespace cru diff --git a/src/common/StringUtil.cpp b/src/common/StringUtil.cpp index 3c312d49..b13c0193 100644 --- a/src/common/StringUtil.cpp +++ b/src/common/StringUtil.cpp @@ -1,4 +1,5 @@ #include "cru/common/StringUtil.hpp" +#include "cru/common/Base.hpp" #include "gsl/gsl_util" namespace cru { @@ -229,4 +230,58 @@ bool Utf16IsValidInsertPosition(std::u16string_view s, gsl::index position) { if (position == static_cast(s.size())) return true; return !IsUtf16SurrogatePairTrailing(s[position]); } + +gsl::index Utf16BackwardUntil(std::u16string_view str, gsl::index position, + const std::function& predicate) { + if (position <= 0) return position; + while (true) { + gsl::index p = position; + auto c = Utf16PreviousCodePoint(str, p, &position); + if (predicate(c)) return p; + if (c == k_invalid_code_point) return p; + } + UnreachableCode(); +} + +gsl::index Utf16ForwardUntil(std::u16string_view str, gsl::index position, + const std::function& predicate) { + if (position >= static_cast(str.size())) return position; + while (true) { + gsl::index p = position; + auto c = Utf16NextCodePoint(str, p, &position); + if (predicate(c)) return p; + if (c == k_invalid_code_point) return p; + } + UnreachableCode(); +} + +inline bool IsSpace(CodePoint c) { return c == 0x20; } + +gsl::index Utf16PreviousWord(std::u16string_view str, gsl::index position, + bool* is_space) { + if (position <= 0) return position; + auto c = Utf16PreviousCodePoint(str, position, nullptr); + if (IsSpace(c)) { // TODO: Currently only test against 0x20(space). + if (is_space) *is_space = true; + return Utf16BackwardUntil(str, position, + [](CodePoint c) { return !IsSpace(c); }); + } else { + if (is_space) *is_space = false; + return Utf16BackwardUntil(str, position, IsSpace); + } +} + +gsl::index Utf16NextWord(std::u16string_view str, gsl::index position, + bool* is_space) { + if (position >= static_cast(str.size())) return position; + auto c = Utf16NextCodePoint(str, position, nullptr); + if (IsSpace(c)) { // TODO: Currently only test against 0x20(space). + if (is_space) *is_space = true; + return Utf16ForwardUntil(str, position, + [](CodePoint c) { return !IsSpace(c); }); + } else { + if (is_space) *is_space = false; + return Utf16ForwardUntil(str, position, IsSpace); + } +} } // namespace cru diff --git a/src/ui/controls/TextHostControlService.cpp b/src/ui/controls/TextHostControlService.cpp index 91ec53ff..4bfd6715 100644 --- a/src/ui/controls/TextHostControlService.cpp +++ b/src/ui/controls/TextHostControlService.cpp @@ -419,6 +419,14 @@ void TextHostControlService::SetUpShortcuts() { return true; }); + shortcut_hub_.RegisterShortcut( + u"CtrlLeft", {KeyCode::Left, KeyModifiers::ctrl}, [this] { + auto text = this->GetTextView(); + auto caret = this->GetCaretPosition(); + this->SetSelection(Utf16PreviousWord(text, caret)); + return true; + }); + shortcut_hub_.RegisterShortcut(u"Right", KeyCode::Right, [this] { auto text = this->GetTextView(); const auto caret = this->GetCaretPosition(); @@ -438,5 +446,13 @@ void TextHostControlService::SetUpShortcuts() { this->SetSelection(selection); return true; }); + + shortcut_hub_.RegisterShortcut( + u"CtrlRight", {KeyCode::Right, KeyModifiers::ctrl}, [this] { + auto text = this->GetTextView(); + auto caret = this->GetCaretPosition(); + this->SetSelection(Utf16NextWord(text, caret)); + return true; + }); } } // namespace cru::ui::controls -- cgit v1.2.3 From 38a5b9eddb75e07695ee38f0bd41d3fd76341a15 Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 28 Feb 2021 14:58:20 +0800 Subject: ... --- include/cru/common/Event.hpp | 70 +++++++++++-------- src/ui/render/ScrollBar.cpp | 160 +++++++++++++++++++++++-------------------- 2 files changed, 127 insertions(+), 103 deletions(-) (limited to 'include/cru/common') diff --git a/include/cru/common/Event.hpp b/include/cru/common/Event.hpp index aa8fadbb..b6999aa4 100644 --- a/include/cru/common/Event.hpp +++ b/include/cru/common/Event.hpp @@ -110,7 +110,7 @@ struct IEvent : virtual IBaseEvent { public: using EventArgs = DeducedEventArgs; using EventHandler = std::function; - using HandlerVariant = std::variant; + using ShortCircuitHandler = std::function; protected: IEvent() = default; @@ -121,24 +121,27 @@ struct IEvent : virtual IBaseEvent { public: virtual EventRevoker AddHandler(EventHandler handler) = 0; + virtual EventRevoker AddShortCircuitHandler(ShortCircuitHandler handler) = 0; + virtual EventRevoker PrependShortCircuitHandler( + ShortCircuitHandler handler) = 0; }; // A non-copyable non-movable Event class. // It stores a list of event handlers. template class Event : public details::EventBase, public IEvent { + using typename IEvent::EventArgs; + using typename IBaseEvent::SpyOnlyHandler; using typename IEvent::EventHandler; - using typename IEvent::HandlerVariant; + using typename IEvent::ShortCircuitHandler; private: struct HandlerData { - HandlerData(EventHandlerToken token, EventHandler handler) - : token(token), handler(handler) {} - HandlerData(EventHandlerToken token, SpyOnlyHandler handler) - : token(token), handler(handler) {} + HandlerData(EventHandlerToken token, ShortCircuitHandler handler) + : token(token), handler(std::move(handler)) {} EventHandlerToken token; - HandlerVariant handler; + ShortCircuitHandler handler; }; public: @@ -148,43 +151,50 @@ class Event : public details::EventBase, public IEvent { ~Event() = default; EventRevoker AddSpyOnlyHandler(SpyOnlyHandler handler) override { + return AddShortCircuitHandler([handler = std::move(handler)](EventArgs) { + handler(); + return false; + }); + } + + EventRevoker AddHandler(EventHandler handler) override { + return AddShortCircuitHandler( + [handler = std::move(handler)](EventArgs args) { + handler(args); + return false; + }); + } + + // Handler return true to short circuit following handlers. + EventRevoker AddShortCircuitHandler(ShortCircuitHandler handler) override { const auto token = current_token_++; this->handler_data_list_.emplace_back(token, std::move(handler)); return CreateRevoker(token); } - EventRevoker AddHandler(EventHandler handler) override { + // Handler return true to short circuit following handlers. + EventRevoker PrependShortCircuitHandler( + ShortCircuitHandler handler) override { const auto token = current_token_++; - this->handler_data_list_.emplace_back(token, std::move(handler)); + this->handler_data_list_.emplace(this->handler_data_list_.cbegin(), token, + std::move(handler)); return CreateRevoker(token); } // This method will make a copy of all handlers. Because user might delete a - // handler in a handler, which may lead to seg fault as the handler is deleted - // while being executed. - // Thanks to this behavior, all handlers will be taken a snapshot when Raise - // is called, so even if you delete a handler during this period, all handlers - // in the snapshot will be executed. - void Raise(typename IEvent::EventArgs args) { - std::vector handlers; + // handler in a handler, which may lead to seg fault as the handler is + // deleted while being executed. Thanks to this behavior, all handlers will + // be taken a snapshot when Raise is called, so even if you delete a handler + // during this period, all handlers in the snapshot will be executed. + void Raise(EventArgs args) { + std::vector handlers; handlers.reserve(this->handler_data_list_.size()); for (const auto& data : this->handler_data_list_) { handlers.push_back(data.handler); } - for (const auto& handler_variant : handlers) { - std::visit( - [&args](auto&& handler) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - handler(); - } else if constexpr (std::is_same_v) { - handler(args); - } else { - static_assert(details::always_false_v, - "non-exhaustive visitor!"); - } - }, - handler_variant); + for (const auto& handler : handlers) { + auto short_circuit = handler(args); + if (short_circuit) return; } } diff --git a/src/ui/render/ScrollBar.cpp b/src/ui/render/ScrollBar.cpp index 70255752..00deb479 100644 --- a/src/ui/render/ScrollBar.cpp +++ b/src/ui/render/ScrollBar.cpp @@ -73,82 +73,96 @@ void ScrollBar::Draw(platform::graphics::IPainter* painter) { void ScrollBar::InstallHandlers(controls::Control* control) { event_guard_.Clear(); if (control != nullptr) { - event_guard_ += control->MouseDownEvent()->Bubble()->AddHandler( - [control, this](event::MouseButtonEventArgs& event) { - if (event.GetButton() == mouse_buttons::left && IsEnabled() && - IsExpanded()) { - auto hit_test_result = - ExpandedHitTest(event.GetPoint(render_object_)); - if (!hit_test_result) return; - - switch (*hit_test_result) { - case ScrollBarAreaKind::UpArrow: - this->scroll_attempt_event_.Raise( - {GetDirection(), ScrollKind::Line, -1}); - event.SetHandled(); - break; - case ScrollBarAreaKind::DownArrow: - this->scroll_attempt_event_.Raise( - {GetDirection(), ScrollKind::Line, 1}); - event.SetHandled(); - break; - case ScrollBarAreaKind::UpSlot: - this->scroll_attempt_event_.Raise( - {GetDirection(), ScrollKind::Page, -1}); - event.SetHandled(); - break; - case ScrollBarAreaKind::DownSlot: - this->scroll_attempt_event_.Raise( - {GetDirection(), ScrollKind::Page, 1}); + event_guard_ += + control->MouseDownEvent()->Bubble()->PrependShortCircuitHandler( + [control, this](event::MouseButtonEventArgs& event) { + if (event.GetButton() == mouse_buttons::left && IsEnabled() && + IsExpanded()) { + auto hit_test_result = + ExpandedHitTest(event.GetPoint(render_object_)); + if (!hit_test_result) return false; + + switch (*hit_test_result) { + case ScrollBarAreaKind::UpArrow: + this->scroll_attempt_event_.Raise( + {GetDirection(), ScrollKind::Line, -1}); + event.SetHandled(); + return true; + case ScrollBarAreaKind::DownArrow: + this->scroll_attempt_event_.Raise( + {GetDirection(), ScrollKind::Line, 1}); + event.SetHandled(); + return true; + case ScrollBarAreaKind::UpSlot: + this->scroll_attempt_event_.Raise( + {GetDirection(), ScrollKind::Page, -1}); + event.SetHandled(); + return true; + case ScrollBarAreaKind::DownSlot: + this->scroll_attempt_event_.Raise( + {GetDirection(), ScrollKind::Page, 1}); + event.SetHandled(); + return true; + case ScrollBarAreaKind::Thumb: { + auto thumb_rect = + GetExpandedAreaRect(ScrollBarAreaKind::Thumb); + assert(thumb_rect); + + if (!control->CaptureMouse()) break; + move_thumb_thumb_original_rect_ = *thumb_rect; + move_thumb_start_ = event.GetPoint(); + event.SetHandled(); + return true; + } + default: + break; + } + } + + return false; + }); + + event_guard_ += + control->MouseUpEvent()->Bubble()->PrependShortCircuitHandler( + [control, this](event::MouseButtonEventArgs& event) { + if (event.GetButton() == mouse_buttons::left && + move_thumb_start_) { + move_thumb_start_ = std::nullopt; + control->ReleaseMouse(); event.SetHandled(); - break; - case ScrollBarAreaKind::Thumb: { - auto thumb_rect = GetExpandedAreaRect(ScrollBarAreaKind::Thumb); - assert(thumb_rect); - - if (!control->CaptureMouse()) break; - move_thumb_thumb_original_rect_ = *thumb_rect; - move_thumb_start_ = event.GetPoint(); + return true; + } + return false; + }); + + event_guard_ += + control->MouseMoveEvent()->Bubble()->PrependShortCircuitHandler( + [this](event::MouseEventArgs& event) { + if (move_thumb_start_) { + auto new_scroll_position = CalculateNewScrollPosition( + move_thumb_thumb_original_rect_, + event.GetPoint() - *move_thumb_start_); + + this->scroll_attempt_event_.Raise({GetDirection(), + ScrollKind::Absolute, + new_scroll_position}); event.SetHandled(); - break; + return true; } - default: - break; - } - } - }); - - event_guard_ += control->MouseUpEvent()->Bubble()->AddHandler( - [control, this](event::MouseButtonEventArgs& event) { - if (event.GetButton() == mouse_buttons::left && move_thumb_start_) { - move_thumb_start_ = std::nullopt; - control->ReleaseMouse(); - event.SetHandled(); - } - }); - - event_guard_ += control->MouseMoveEvent()->Bubble()->AddHandler( - [this](event::MouseEventArgs& event) { - if (move_thumb_start_) { - auto new_scroll_position = CalculateNewScrollPosition( - move_thumb_thumb_original_rect_, - event.GetPoint() - *move_thumb_start_); - - this->scroll_attempt_event_.Raise( - {GetDirection(), ScrollKind::Absolute, new_scroll_position}); - event.SetHandled(); - return; - } - - if (IsEnabled() && !IsExpanded()) { - auto trigger_expand_area = GetCollapsedTriggerExpandAreaRect(); - if (trigger_expand_area && - trigger_expand_area->IsPointInside( - event.GetPoint(this->render_object_))) - SetExpanded(true); - event.SetHandled(); - } - }); + + if (IsEnabled() && !IsExpanded()) { + auto trigger_expand_area = GetCollapsedTriggerExpandAreaRect(); + if (trigger_expand_area && + trigger_expand_area->IsPointInside( + event.GetPoint(this->render_object_))) { + SetExpanded(true); + event.SetHandled(); + return true; + } + } + + return false; + }); } } -- cgit v1.2.3