diff options
author | crupest <crupest@outlook.com> | 2021-03-24 19:14:19 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2021-03-24 19:14:19 +0800 |
commit | 7f15a1ff9a2007e119798053083a0a87d042990a (patch) | |
tree | cb35c01a7eaee867376d959b96c9bbd15df939e5 /include/cru | |
parent | 74956951ee663012df0c3fe4ebe29799cb2f7732 (diff) | |
parent | 7703063a5816b089483e78ccd74bb9902ccfbea8 (diff) | |
download | cru-7f15a1ff9a2007e119798053083a0a87d042990a.tar.gz cru-7f15a1ff9a2007e119798053083a0a87d042990a.tar.bz2 cru-7f15a1ff9a2007e119798053083a0a87d042990a.zip |
Merge branch 'master' of https://github.com/crupest/CruUI
Diffstat (limited to 'include/cru')
99 files changed, 2531 insertions, 864 deletions
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 <exception> #include <gsl/gsl> - #include <stdexcept> #define CRU_UNUSED(entity) static_cast<void>(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 <class T> +inline void hash_combine(std::size_t& s, const T& v) { + std::hash<T> 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/ClonablePtr.hpp b/include/cru/common/ClonablePtr.hpp new file mode 100644 index 00000000..5e4b80c9 --- /dev/null +++ b/include/cru/common/ClonablePtr.hpp @@ -0,0 +1,204 @@ +#pragma once + +#include <cstddef> +#include <functional> +#include <memory> +#include <type_traits> + +namespace cru { +template <typename TClonable> +class ClonablePtr { + template <typename T> + friend class ClonablePtr; + + public: + using element_type = typename std::unique_ptr<TClonable>::element_type; + using pointer = typename std::unique_ptr<TClonable>::pointer; + + ClonablePtr() = default; + ClonablePtr(std::nullptr_t) noexcept : ptr_(nullptr) {} + explicit ClonablePtr(pointer p) noexcept : ptr_(p) {} + ClonablePtr(std::unique_ptr<element_type>&& p) noexcept + : ptr_(std::move(p)) {} + template <typename O, + std::enable_if_t<std::is_convertible_v< + typename ClonablePtr<O>::pointer, pointer>, + int> = 0> + ClonablePtr(std::unique_ptr<O>&& p) : ptr_(std::move(p)) {} + ClonablePtr(const ClonablePtr& other) : ptr_(other.ptr_->Clone()) {} + ClonablePtr(ClonablePtr&& other) = default; + template <typename O, + std::enable_if_t<std::is_convertible_v< + typename ClonablePtr<O>::pointer, pointer>, + int> = 0> + ClonablePtr(const ClonablePtr<O>& other) : ptr_(other.ptr_->Clone()) {} + template <typename O, + std::enable_if_t<std::is_convertible_v< + typename ClonablePtr<O>::pointer, pointer>, + int> = 0> + ClonablePtr(ClonablePtr<O>&& other) noexcept : ptr_(std::move(other.ptr_)) {} + ClonablePtr& operator=(std::nullptr_t) noexcept { + ptr_ = nullptr; + return *this; + } + ClonablePtr& operator=(std::unique_ptr<element_type>&& other) noexcept { + ptr_ = std::move(other); + return *this; + } + template <typename O, + std::enable_if_t<std::is_convertible_v< + typename ClonablePtr<O>::pointer, pointer>, + int> = 0> + ClonablePtr& operator=(std::unique_ptr<O>&& p) noexcept { + ptr_ = std::move(p); + return *this; + } + ClonablePtr& operator=(const ClonablePtr& other) { + if (this != &other) { + ptr_ = std::unique_ptr<element_type>(other.ptr_->Clone()); + } + return *this; + } + ClonablePtr& operator=(ClonablePtr&& other) = default; + template <typename O, + std::enable_if_t<std::is_convertible_v< + typename ClonablePtr<O>::pointer, pointer>, + int> = 0> + ClonablePtr& operator=(const ClonablePtr<O>& other) noexcept { + if (this != &other) { + ptr_ = std::unique_ptr<element_type>(other.ptr_->Clone()); + } + return *this; + } + template <typename O, + std::enable_if_t<std::is_convertible_v< + typename ClonablePtr<O>::pointer, pointer>, + int> = 0> + ClonablePtr& operator=(ClonablePtr<O>&& 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<element_type> ptr_; +}; + +template <typename T> +void swap(ClonablePtr<T>& left, ClonablePtr<T>& right) noexcept { + left.swap(right); +} + +template <typename T> +bool operator==(const ClonablePtr<T>& left, const ClonablePtr<T>& right) { + return left.get() == right.get(); +} + +template <typename T> +bool operator!=(const ClonablePtr<T>& left, const ClonablePtr<T>& right) { + return left.get() != right.get(); +} + +template <typename T> +bool operator<(const ClonablePtr<T>& left, const ClonablePtr<T>& right) { + return left.get() < right.get(); +} + +template <typename T> +bool operator<=(const ClonablePtr<T>& left, const ClonablePtr<T>& right) { + return left.get() <= right.get(); +} + +template <typename T> +bool operator>(const ClonablePtr<T>& left, const ClonablePtr<T>& right) { + return left.get() > right.get(); +} + +template <typename T> +bool operator>=(const ClonablePtr<T>& left, const ClonablePtr<T>& right) { + return left.get() >= right.get(); +} + +template <typename T> +bool operator==(const ClonablePtr<T>& left, std::nullptr_t) { + return left.get() == nullptr; +} + +template <typename T> +bool operator!=(const ClonablePtr<T>& left, std::nullptr_t) { + return left.get() != nullptr; +} + +template <typename T> +bool operator<(const ClonablePtr<T>& left, std::nullptr_t) { + return left.get() < nullptr; +} + +template <typename T> +bool operator<=(const ClonablePtr<T>& left, std::nullptr_t) { + return left.get() <= nullptr; +} + +template <typename T> +bool operator>(const ClonablePtr<T>& left, std::nullptr_t) { + return left.get() > nullptr; +} + +template <typename T> +bool operator>=(const ClonablePtr<T>& left, std::nullptr_t) { + return left.get() >= nullptr; +} + +template <typename T> +bool operator==(std::nullptr_t, const ClonablePtr<T>& right) { + return nullptr == right.get(); +} + +template <typename T> +bool operator!=(std::nullptr_t, const ClonablePtr<T>& right) { + return nullptr != right.get(); +} + +template <typename T> +bool operator<(std::nullptr_t, const ClonablePtr<T>& right) { + return nullptr < right.get(); +} + +template <typename T> +bool operator<=(std::nullptr_t, const ClonablePtr<T>& right) { + return nullptr <= right.get(); +} + +template <typename T> +bool operator>(std::nullptr_t, const ClonablePtr<T>& right) { + return nullptr > right.get(); +} + +template <typename T> +bool operator>=(std::nullptr_t, const ClonablePtr<T>& right) { + return nullptr >= right.get(); +} + +} // namespace cru + +namespace std { +template <typename T> +struct hash<cru::ClonablePtr<T>> { + std::size_t operator()(const cru::ClonablePtr<T>& p) const { + return std::hash<typename cru::ClonablePtr<T>::pointer>(p.get()); + } +}; +} // namespace std diff --git a/include/cru/common/Event.hpp b/include/cru/common/Event.hpp index 377ca7f3..b6999aa4 100644 --- a/include/cru/common/Event.hpp +++ b/include/cru/common/Event.hpp @@ -5,14 +5,19 @@ #include <algorithm> #include <functional> +#include <initializer_list> #include <memory> #include <utility> +#include <variant> #include <vector> namespace cru { class EventRevoker; namespace details { +template <class> +inline constexpr bool always_false_v = false; + // Base class of all Event<T...>. // It erases event args types and provides a // unified form to create event revoker and @@ -24,10 +29,8 @@ class EventBase : public SelfResolvable<EventBase> { 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 @@ -84,78 +87,114 @@ using DeducedEventArgs = std::conditional_t< std::is_lvalue_reference_v<TRaw>, TRaw, std::conditional_t<std::is_scalar_v<TRaw>, 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<void()>; + + public: + virtual EventRevoker AddSpyOnlyHandler(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 <typename TEventArgs> -struct IEvent { +struct IEvent : virtual IBaseEvent { public: using EventArgs = DeducedEventArgs<TEventArgs>; using EventHandler = std::function<void(EventArgs)>; + using ShortCircuitHandler = std::function<bool(EventArgs)>; 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; + 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 <typename TEventArgs> class Event : public details::EventBase, public IEvent<TEventArgs> { + using typename IEvent<TEventArgs>::EventArgs; + + using typename IBaseEvent::SpyOnlyHandler; using typename IEvent<TEventArgs>::EventHandler; + using typename IEvent<TEventArgs>::ShortCircuitHandler; private: struct HandlerData { - HandlerData(EventHandlerToken token, EventHandler handler) - : token(token), handler(handler) {} + HandlerData(EventHandlerToken token, ShortCircuitHandler handler) + : token(token), handler(std::move(handler)) {} EventHandlerToken token; - EventHandler handler; + ShortCircuitHandler 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 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, handler); + 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<TEventArgs>::EventArgs args) { - std::vector<EventHandler> 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<ShortCircuitHandler> 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); + auto short_circuit = handler(args); + if (short_circuit) return; } } @@ -183,6 +222,7 @@ struct EventRevokerDestroyer { }; } // namespace details +// A guard class for event revoker. Automatically revoke it when destroyed. class EventRevokerGuard { public: EventRevokerGuard() = default; @@ -201,7 +241,9 @@ class EventRevokerGuard { return *revoker_; } - void Release() { revoker_.release(); } + EventRevoker Release() { return std::move(*revoker_.release()); } + + void Reset() { revoker_.reset(); } void Reset(EventRevoker&& revoker) { revoker_.reset(new EventRevoker(std::move(revoker))); @@ -209,5 +251,32 @@ class EventRevokerGuard { private: std::unique_ptr<EventRevoker, details::EventRevokerDestroyer> 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; + } + + void Clear() { event_revoker_guard_list_.clear(); } + + bool IsEmpty() const { return event_revoker_guard_list_.empty(); } + + private: + std::vector<EventRevokerGuard> event_revoker_guard_list_; +}; } // namespace cru 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 <array> +#include <charconv> +#include <string> +#include <string_view> +#include <system_error> +#include <type_traits> + +namespace cru { +template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> +std::u16string ToUtf16String(T number) { + std::array<char, 40> 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/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 <algorithm> +#include <functional> +#include <utility> +#include <vector> + +namespace cru { + +template <typename T> +class HandlerRegistryIterator { + public: + using RawIterator = + typename std::vector<std::pair<int, std::function<T>>>::const_iterator; + + explicit HandlerRegistryIterator(RawIterator raw) : raw_(std::move(raw)) {} + + CRU_DELETE_COPY(HandlerRegistryIterator) + CRU_DELETE_MOVE(HandlerRegistryIterator) + + ~HandlerRegistryIterator() = default; + + const std::function<T>& operator*() const { return raw_->second; } + const std::function<T>* operator->() const { return &raw_->second; } + + HandlerRegistryIterator& operator++() { + ++raw_; + return *this; + } + + HandlerRegistryIterator operator++(int) { + auto c = *this; + this->operator++(); + return c; + } + + bool operator==(const HandlerRegistryIterator<T>& other) const { + return this->raw_ == other.raw_; + } + + bool operator!=(const HandlerRegistryIterator<T>& other) const { + return !this->operator==(other); + } + + private: + RawIterator raw_; +}; + +template <typename T> +class HandlerRegistry final { + public: + HandlerRegistry() = default; + CRU_DEFAULT_COPY(HandlerRegistry) + CRU_DEFAULT_MOVE(HandlerRegistry) + ~HandlerRegistry() = default; + + public: + int AddHandler(std::function<T> 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<int, std::function<T>>& d) { + return d.first == id; + }); + if (result != handler_list_.cend()) { + handler_list_.erase(result); + } + } + + HandlerRegistryIterator<T> begin() const { + return HandlerRegistryIterator<T>(handler_list_.begin()); + } + + HandlerRegistryIterator<T> end() const { + return HandlerRegistryIterator<T>(handler_list_.begin()); + } + + private: + int current_id_ = 1; + std::vector<std::pair<int, std::function<T>>> handler_list_; +}; +} // 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<std::unique_ptr<ILogSource>> sources_; }; +// TODO: Remove argument evaluation in Debug. template <typename... TArgs> void Debug([[maybe_unused]] TArgs&&... args) { #ifdef CRU_DEBUG @@ -66,6 +67,7 @@ void Error(TArgs&&... args) { fmt::format(std::forward<TArgs>(args)...)); } +// TODO: Remove argument evaluation in Debug. template <typename... TArgs> void TagDebug([[maybe_unused]] std::u16string_view tag, [[maybe_unused]] TArgs&&... args) { 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<T*>(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<T*>(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<T*>(this); + } + return *this; + } + + virtual ~SelfResolvable() { + if (resolver_ != nullptr) (*resolver_) = nullptr; + } ObjectResolver<T> CreateResolver() { return ObjectResolver<T>(resolver_); } diff --git a/include/cru/common/StringUtil.hpp b/include/cru/common/StringUtil.hpp index 5dacfa12..62999d53 100644 --- a/include/cru/common/StringUtil.hpp +++ b/include/cru/common/StringUtil.hpp @@ -1,6 +1,10 @@ #pragma once #include "Base.hpp" +#include <functional> +#include <string> +#include <string_view> + namespace cru { using CodePoint = std::int32_t; constexpr CodePoint k_invalid_code_point = -1; @@ -124,4 +128,21 @@ 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); + +// 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<bool(CodePoint)>& 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<bool(CodePoint)>& 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/include/cru/platform/GraphBase.hpp b/include/cru/platform/GraphBase.hpp index 186ee9d0..6bf2736f 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 <fmt/core.h> #include <cstdint> #include <limits> #include <optional> +#include <string> #include <utility> namespace cru::platform { @@ -14,6 +18,16 @@ struct Point final { constexpr Point(const float x, const float y) : x(x), y(y) {} explicit constexpr Point(const Size& size); + std::u16string ToDebugString() const { + return fmt::format(u"({}, {})", ToUtf16String(x), ToUtf16String(y)); + } + + constexpr Point& operator+=(const Point& other) { + this->x += other.x; + this->y += other.y; + return *this; + } + float x = 0; float y = 0; }; @@ -46,6 +60,11 @@ struct Size final { std::numeric_limits<float>::max()}; } + std::u16string ToDebugString() const { + return fmt::format(u"({}, {})", ToUtf16String(width), + ToUtf16String(height)); + } + float width = 0; float height = 0; }; @@ -258,7 +277,7 @@ struct TextRange final { gsl::index GetStart() const { return position; } gsl::index GetEnd() const { return position + count; } - void AdjustEnd(gsl::index new_end) { count = new_end - position; } + void ChangeEnd(gsl::index new_end) { count = new_end - position; } TextRange Normalize() const { auto result = *this; @@ -297,6 +316,12 @@ struct Color { (hex >> 24) & mask); } + constexpr Color WithAlpha(std::uint8_t new_alpha) const { + auto result = *this; + result.alpha = new_alpha; + return result; + } + std::uint8_t red; std::uint8_t green; std::uint8_t blue; diff --git a/include/cru/platform/Matrix.hpp b/include/cru/platform/Matrix.hpp index e702df90..8ec5faaa 100644 --- a/include/cru/platform/Matrix.hpp +++ b/include/cru/platform/Matrix.hpp @@ -50,10 +50,15 @@ struct Matrix { return Matrix{1.0f, 0.0f, 0.0f, 1.0f, x, y}; } + static Matrix Translation(const Point& point) { + return Translation(point.x, point.y); + } + static Matrix Scale(float sx, float sy) { return Matrix{sx, 0.0f, 0.0f, sy, 0.0f, 0.0f}; } + // Clockwise. static Matrix Rotation(float angle) { float r = AngleToRadian(angle); float s = std::sin(r); diff --git a/include/cru/platform/graph/Base.hpp b/include/cru/platform/graphics/Base.hpp index 61cfc5ef..e751ebdb 100644 --- a/include/cru/platform/graph/Base.hpp +++ b/include/cru/platform/graphics/Base.hpp @@ -5,7 +5,7 @@ #include <memory> -namespace cru::platform::graph { +namespace cru::platform::graphics { // forward declarations struct IGraphFactory; struct IBrush; diff --git a/include/cru/platform/graph/Brush.hpp b/include/cru/platform/graphics/Brush.hpp index e67384de..10c666b5 100644 --- a/include/cru/platform/graph/Brush.hpp +++ b/include/cru/platform/graphics/Brush.hpp @@ -1,7 +1,7 @@ #pragma once #include "Resource.hpp" -namespace cru::platform::graph { +namespace cru::platform::graphics { struct IBrush : virtual IGraphResource {}; struct ISolidColorBrush : virtual IBrush { diff --git a/include/cru/platform/graph/Factory.hpp b/include/cru/platform/graphics/Factory.hpp index b4e68f12..f9018e13 100644 --- a/include/cru/platform/graph/Factory.hpp +++ b/include/cru/platform/graphics/Factory.hpp @@ -9,7 +9,7 @@ #include <string> #include <string_view> -namespace cru::platform::graph { +namespace cru::platform::graphics { // Entry point of the graph module. struct IGraphFactory : virtual INativeResource { virtual std::unique_ptr<ISolidColorBrush> CreateSolidColorBrush() = 0; @@ -21,5 +21,11 @@ struct IGraphFactory : virtual INativeResource { virtual std::unique_ptr<ITextLayout> CreateTextLayout( std::shared_ptr<IFont> font, std::u16string text) = 0; + + std::unique_ptr<ISolidColorBrush> CreateSolidColorBrush(const Color& color) { + std::unique_ptr<ISolidColorBrush> brush = CreateSolidColorBrush(); + brush->SetColor(color); + return brush; + } }; -} // namespace cru::platform::graph +} // namespace cru::platform::graphics diff --git a/include/cru/platform/graph/Font.hpp b/include/cru/platform/graphics/Font.hpp index 182cc15b..70392a69 100644 --- a/include/cru/platform/graph/Font.hpp +++ b/include/cru/platform/graphics/Font.hpp @@ -1,7 +1,7 @@ #pragma once #include "Resource.hpp" -namespace cru::platform::graph { +namespace cru::platform::graphics { struct IFont : virtual IGraphResource { virtual float GetFontSize() = 0; }; diff --git a/include/cru/platform/graph/Geometry.hpp b/include/cru/platform/graphics/Geometry.hpp index 354efd97..b0ce6ad9 100644 --- a/include/cru/platform/graph/Geometry.hpp +++ b/include/cru/platform/graphics/Geometry.hpp @@ -1,7 +1,7 @@ #pragma once #include "Resource.hpp" -namespace cru::platform::graph { +namespace cru::platform::graphics { struct IGeometry : virtual IGraphResource { virtual bool FillContains(const Point& point) = 0; }; diff --git a/include/cru/platform/graph/Painter.hpp b/include/cru/platform/graphics/Painter.hpp index 27ae420b..f75ea52b 100644 --- a/include/cru/platform/graph/Painter.hpp +++ b/include/cru/platform/graphics/Painter.hpp @@ -1,7 +1,7 @@ #pragma once #include "Resource.hpp" -namespace cru::platform::graph { +namespace cru::platform::graphics { struct IPainter : virtual INativeResource { virtual Matrix GetTransform() = 0; @@ -9,6 +9,8 @@ struct IPainter : virtual INativeResource { virtual void Clear(const Color& color) = 0; + virtual void DrawLine(const Point& start, const Point& end, IBrush* brush, + float width) = 0; virtual void StrokeRectangle(const Rect& rectangle, IBrush* brush, float width) = 0; virtual void FillRectangle(const Rect& rectangle, IBrush* brush) = 0; @@ -26,4 +28,4 @@ struct IPainter : virtual INativeResource { virtual void EndDraw() = 0; }; -} // namespace cru::platform::graph +} // namespace cru::platform::graphics diff --git a/include/cru/platform/graph/Resource.hpp b/include/cru/platform/graphics/Resource.hpp index 8859360c..a1625ce4 100644 --- a/include/cru/platform/graph/Resource.hpp +++ b/include/cru/platform/graphics/Resource.hpp @@ -1,7 +1,7 @@ #pragma once #include "Base.hpp" -namespace cru::platform::graph { +namespace cru::platform::graphics { struct IGraphFactory; struct IGraphResource : virtual INativeResource { diff --git a/include/cru/platform/graph/TextLayout.hpp b/include/cru/platform/graphics/TextLayout.hpp index a101983f..b363fb77 100644 --- a/include/cru/platform/graph/TextLayout.hpp +++ b/include/cru/platform/graphics/TextLayout.hpp @@ -4,7 +4,7 @@ #include <string> #include <vector> -namespace cru::platform::graph { +namespace cru::platform::graphics { struct ITextLayout : virtual IGraphResource { virtual std::u16string GetText() = 0; virtual std::u16string_view GetTextView() = 0; @@ -16,9 +16,9 @@ struct ITextLayout : virtual IGraphResource { virtual void SetMaxWidth(float max_width) = 0; virtual void SetMaxHeight(float max_height) = 0; - virtual Rect GetTextBounds() = 0; + virtual Rect GetTextBounds(bool includingTrailingSpace = false) = 0; virtual std::vector<Rect> TextRangeRect(const TextRange& text_range) = 0; virtual Point TextSinglePoint(Index position, bool trailing) = 0; virtual TextHitTestResult HitTest(const Point& point) = 0; }; -} // namespace cru::platform::graph +} // namespace cru::platform::graphics diff --git a/include/cru/platform/graph/util/Painter.hpp b/include/cru/platform/graphics/util/Painter.hpp index f9aec027..90457cf4 100644 --- a/include/cru/platform/graph/util/Painter.hpp +++ b/include/cru/platform/graphics/util/Painter.hpp @@ -4,14 +4,14 @@ #include <functional> #include <type_traits> -namespace cru::platform::graph::util { +namespace cru::platform::graphics::util { template <typename Fn> void WithTransform(IPainter* painter, const Matrix& matrix, const Fn& action) { static_assert(std::is_invocable_v<decltype(action), IPainter*>, "Action must can be be invoked with painter."); const auto old = painter->GetTransform(); - painter->SetTransform(old * matrix); + painter->SetTransform(matrix * old); action(painter); painter->SetTransform(old); } -} // namespace cru::platform::graph::util +} // namespace cru::platform::graphics::util diff --git a/include/cru/platform/native/Base.hpp b/include/cru/platform/gui/Base.hpp index bba7b960..7a9d1889 100644 --- a/include/cru/platform/native/Base.hpp +++ b/include/cru/platform/gui/Base.hpp @@ -1,23 +1,18 @@ #pragma once +#include "Keyboard.hpp" #include "cru/common/Base.hpp" #include "cru/common/Bitmask.hpp" -#include "cru/platform/graph/Base.hpp" -#include "Keyboard.hpp" +#include "cru/platform/graphics/Base.hpp" -namespace cru::platform::native { +#include "../Resource.hpp" + +namespace cru::platform::gui { struct ICursor; struct ICursorManager; struct IUiApplication; struct INativeWindow; -struct INativeWindowResolver; -struct IInputMethodManager; struct IInputMethodContext; -struct Dpi { - float x; - float y; -}; - namespace details { struct TagMouseButton {}; } // namespace details @@ -30,11 +25,6 @@ constexpr MouseButton middle{0b10}; constexpr MouseButton right{0b100}; } // namespace mouse_buttons -enum class SystemCursorType { - Arrow, - Hand, -}; - struct NativeMouseButtonEventArgs { MouseButton button; Point point; @@ -49,4 +39,4 @@ struct NativeKeyEventArgs { enum class FocusChangeType { Gain, Lost }; enum class MouseEnterLeaveType { Enter, Leave }; -} // namespace cru::platform::native +} // namespace cru::platform::gui diff --git a/include/cru/platform/native/Cursor.hpp b/include/cru/platform/gui/Cursor.hpp index 6c8f8068..316496a0 100644 --- a/include/cru/platform/native/Cursor.hpp +++ b/include/cru/platform/gui/Cursor.hpp @@ -1,10 +1,11 @@ #pragma once -#include "../Resource.hpp" #include "Base.hpp" #include <memory> -namespace cru::platform::native { +namespace cru::platform::gui { +enum class SystemCursorType { Arrow, Hand, IBeam }; + struct ICursor : virtual INativeResource {}; struct ICursorManager : virtual INativeResource { @@ -12,4 +13,4 @@ struct ICursorManager : virtual INativeResource { // TODO: Add method to create cursor. }; -} // namespace cru::platform::native +} // namespace cru::platform::gui diff --git a/include/cru/platform/gui/DebugFlags.hpp b/include/cru/platform/gui/DebugFlags.hpp new file mode 100644 index 00000000..2b7c7c19 --- /dev/null +++ b/include/cru/platform/gui/DebugFlags.hpp @@ -0,0 +1,8 @@ +#pragma once + +namespace cru::platform::gui { +struct DebugFlags { + static constexpr int paint = 0; + static constexpr int input_method = 0; +}; +} // namespace cru::platform::gui diff --git a/include/cru/platform/native/InputMethod.hpp b/include/cru/platform/gui/InputMethod.hpp index 6f222a43..9d090eab 100644 --- a/include/cru/platform/native/InputMethod.hpp +++ b/include/cru/platform/gui/InputMethod.hpp @@ -1,5 +1,4 @@ #pragma once -#include "../Resource.hpp" #include "Base.hpp" #include "cru/common/Event.hpp" @@ -8,7 +7,7 @@ #include <memory> #include <vector> -namespace cru::platform::native { +namespace cru::platform::gui { struct CompositionClause { int start; int end; @@ -38,7 +37,8 @@ struct IInputMethodContext : virtual INativeResource { virtual CompositionText GetCompositionText() = 0; - // Set the candidate window lefttop. Use this method to prepare typing. + // Set the candidate window lefttop. Relative to window lefttop. Use this + // method to prepare typing. virtual void SetCandidateWindowPosition(const Point& point) = 0; // Triggered when user starts composition. @@ -52,22 +52,17 @@ struct IInputMethodContext : virtual INativeResource { virtual IEvent<std::u16string_view>* TextEvent() = 0; }; - -struct IInputMethodManager : virtual INativeResource { - virtual std::unique_ptr<IInputMethodContext> GetContext( - INativeWindow* window) = 0; -}; -} // namespace cru::platform::native +} // namespace cru::platform::gui template <> -struct fmt::formatter<cru::platform::native::CompositionText, char16_t> +struct fmt::formatter<cru::platform::gui::CompositionText, char16_t> : fmt::formatter<std::u16string_view, char16_t> { auto parse(fmt::basic_format_parse_context<char16_t>& ctx) { return fmt::formatter<std::u16string_view, char16_t>::parse(ctx); } template <typename FormatContext> - auto format(const cru::platform::native::CompositionText& ct, + auto format(const cru::platform::gui::CompositionText& ct, FormatContext& ctx) { auto output = ctx.out(); output = format_to(output, u"text: {}\n", ct.text); diff --git a/include/cru/platform/native/Keyboard.hpp b/include/cru/platform/gui/Keyboard.hpp index 83c61bcc..6c29239b 100644 --- a/include/cru/platform/native/Keyboard.hpp +++ b/include/cru/platform/gui/Keyboard.hpp @@ -1,7 +1,10 @@ #pragma once #include "cru/common/Bitmask.hpp" -namespace cru::platform::native { +#include <string> +#include <string_view> + +namespace cru::platform::gui { // Because of the complexity of keyboard layout, I only add code in US keyboard // layout, the most widely used layout in China. We should try to make it easy // to add new keyboard layout. @@ -113,8 +116,13 @@ struct TagKeyModifier {}; using KeyModifier = Bitmask<details::TagKeyModifier>; struct KeyModifiers { + static constexpr KeyModifier none{0}; static constexpr KeyModifier shift{0b1}; static constexpr KeyModifier ctrl{0b10}; static constexpr KeyModifier alt{0b100}; }; -} // namespace cru::platform::native + +std::u16string_view ToString(KeyCode key_code); +std::u16string ToString(KeyModifier key_modifier, + std::u16string_view separator = u"+"); +} // namespace cru::platform::gui diff --git a/include/cru/platform/gui/UiApplication.hpp b/include/cru/platform/gui/UiApplication.hpp new file mode 100644 index 00000000..5a5b0b13 --- /dev/null +++ b/include/cru/platform/gui/UiApplication.hpp @@ -0,0 +1,135 @@ +#pragma once +#include "Base.hpp" + +#include "cru/common/Bitmask.hpp" + +#include <chrono> +#include <functional> +#include <memory> +#include <vector> + +namespace cru::platform::gui { +namespace details { +struct CreateWindowFlagTag; +} + +using CreateWindowFlag = Bitmask<details::CreateWindowFlagTag>; + +struct CreateWindowFlags { + static constexpr CreateWindowFlag NoCaptionAndBorder{0b1}; +}; + +// The entry point of a ui application. +struct IUiApplication : public virtual INativeResource { + public: + static IUiApplication* GetInstance() { return instance; } + + private: + static IUiApplication* instance; + + protected: + IUiApplication(); + + public: + ~IUiApplication() override; + + // Block current thread and run the message loop. Return the exit code when + // message loop gets a quit message (possibly posted by method RequestQuit). + virtual int Run() = 0; + + // Post a quit message with given quit code. + virtual void RequestQuit(int quit_code) = 0; + + virtual void AddOnQuitHandler(std::function<void()> handler) = 0; + + // Timer id should always be positive (not 0) and never the same. So it's ok + // to use negative value (or 0) to represent no timer. + virtual long long SetImmediate(std::function<void()> action) = 0; + virtual long long SetTimeout(std::chrono::milliseconds milliseconds, + std::function<void()> action) = 0; + virtual long long SetInterval(std::chrono::milliseconds milliseconds, + std::function<void()> action) = 0; + // Implementation should guarantee calls on timer id already canceled have no + // effects and do not crash. Also canceling negative id or 0 should always + // result in no-op. + virtual void CancelTimer(long long id) = 0; + + virtual std::vector<INativeWindow*> GetAllWindow() = 0; + + INativeWindow* CreateWindow(INativeWindow* parent) { + return this->CreateWindow(parent, CreateWindowFlag(0)); + }; + virtual INativeWindow* CreateWindow(INativeWindow* parent, + CreateWindowFlag flags) = 0; + + virtual cru::platform::graphics::IGraphFactory* GetGraphFactory() = 0; + + virtual ICursorManager* GetCursorManager() = 0; +}; + +class TimerAutoCanceler { + public: + TimerAutoCanceler() : id_(0) {} + explicit TimerAutoCanceler(long long id) : id_(id) {} + + CRU_DELETE_COPY(TimerAutoCanceler) + + TimerAutoCanceler(TimerAutoCanceler&& other) : id_(other.id_) { + other.id_ = 0; + } + + TimerAutoCanceler& operator=(TimerAutoCanceler&& other) { + if (&other == this) { + return *this; + } + Reset(other.id_); + other.id_ = 0; + return *this; + } + + TimerAutoCanceler& operator=(long long other) { + return this->operator=(TimerAutoCanceler(other)); + } + + ~TimerAutoCanceler() { Reset(); } + + long long Release() { + auto temp = id_; + id_ = 0; + return temp; + } + + void Reset(long long id = 0) { + if (id_ > 0) IUiApplication::GetInstance()->CancelTimer(id_); + id_ = id; + } + + explicit operator bool() const { return id_; } + + private: + long long id_; +}; + +class TimerListAutoCanceler { + public: + TimerListAutoCanceler() = default; + CRU_DELETE_COPY(TimerListAutoCanceler) + CRU_DEFAULT_MOVE(TimerListAutoCanceler) + ~TimerListAutoCanceler() = default; + + TimerListAutoCanceler& operator+=(long long id) { + list_.push_back(TimerAutoCanceler(id)); + return *this; + } + + void Clear() { list_.clear(); } + + bool IsEmpty() const { return list_.empty(); } + + private: + std::vector<TimerAutoCanceler> list_; +}; + +// Bootstrap from this. +std::unique_ptr<IUiApplication> CreateUiApplication(); +} // namespace cru::platform::gui diff --git a/include/cru/platform/native/Window.hpp b/include/cru/platform/gui/Window.hpp index 1fcac1fc..26d1a476 100644 --- a/include/cru/platform/native/Window.hpp +++ b/include/cru/platform/gui/Window.hpp @@ -1,21 +1,14 @@ #pragma once -#include "../Resource.hpp" #include "Base.hpp" + #include "cru/common/Event.hpp" #include <string_view> -namespace cru::platform::native { +namespace cru::platform::gui { // Represents a native window, which exposes some low-level events and // operations. -// -// Usually you save an INativeWindowResolver after creating a window. Because -// window may be destroyed when user do certain actions like click the close -// button. Then the INativeWindow instance is destroyed and -// INativeWindowResolver::Resolve return nullptr to indicate the fact. struct INativeWindow : virtual INativeResource { - virtual std::shared_ptr<INativeWindowResolver> GetResolver() = 0; - virtual void Close() = 0; virtual INativeWindow* GetParent() = 0; @@ -45,8 +38,9 @@ struct INativeWindow : virtual INativeResource { virtual void RequestRepaint() = 0; // Remember to call EndDraw on return value and destroy it. - virtual std::unique_ptr<graph::IPainter> BeginPaint() = 0; + virtual std::unique_ptr<graphics::IPainter> BeginPaint() = 0; + // Don't use this instance after receive this event. virtual IEvent<std::nullptr_t>* DestroyEvent() = 0; virtual IEvent<std::nullptr_t>* PaintEvent() = 0; virtual IEvent<Size>* ResizeEvent() = 0; @@ -57,11 +51,7 @@ struct INativeWindow : virtual INativeResource { virtual IEvent<NativeMouseButtonEventArgs>* MouseUpEvent() = 0; virtual IEvent<NativeKeyEventArgs>* KeyDownEvent() = 0; virtual IEvent<NativeKeyEventArgs>* KeyUpEvent() = 0; -}; -// See INativeWindow for more info. -struct INativeWindowResolver : virtual INativeResource { - // Think twice before you save the return value. - virtual INativeWindow* Resolve() = 0; + virtual IInputMethodContext* GetInputMethodContext() = 0; }; -} // namespace cru::platform::native +} // namespace cru::platform::gui diff --git a/include/cru/platform/native/UiApplication.hpp b/include/cru/platform/native/UiApplication.hpp deleted file mode 100644 index 1aa4df57..00000000 --- a/include/cru/platform/native/UiApplication.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once -#include "../Resource.hpp" -#include "Base.hpp" - -#include <chrono> -#include <functional> -#include <memory> -#include <vector> - -namespace cru::platform::native { -// The entry point of a ui application. -struct IUiApplication : public virtual INativeResource { - public: - static IUiApplication* GetInstance() { return instance; } - - private: - static IUiApplication* instance; - - protected: - IUiApplication(); - - public: - ~IUiApplication() override; - - // Block current thread and run the message loop. Return the exit code when - // message loop gets a quit message (possibly posted by method RequestQuit). - virtual int Run() = 0; - - // Post a quit message with given quit code. - virtual void RequestQuit(int quit_code) = 0; - - virtual void AddOnQuitHandler(std::function<void()> handler) = 0; - - virtual void InvokeLater(std::function<void()> action) = 0; - // Timer id should always be positive and never the same. So it's ok to use - // negative value to represent no timer. - virtual long long SetTimeout(std::chrono::milliseconds milliseconds, - std::function<void()> action) = 0; - virtual long long SetInterval(std::chrono::milliseconds milliseconds, - std::function<void()> action) = 0; - // Implementation should guarantee calls on timer id already canceled have no - // effects and do not crash. Also canceling negative id should always result - // in no-op. - virtual void CancelTimer(long long id) = 0; - - virtual std::vector<INativeWindow*> GetAllWindow() = 0; - virtual std::shared_ptr<INativeWindowResolver> CreateWindow( - INativeWindow* parent) = 0; - - virtual cru::platform::graph::IGraphFactory* GetGraphFactory() = 0; - - virtual ICursorManager* GetCursorManager() = 0; - virtual IInputMethodManager* GetInputMethodManager() = 0; -}; - -// Bootstrap from this. -std::unique_ptr<IUiApplication> CreateUiApplication(); -} // namespace cru::platform::native diff --git a/include/cru/ui/Base.hpp b/include/cru/ui/Base.hpp index 6be359ab..fbdfec77 100644 --- a/include/cru/ui/Base.hpp +++ b/include/cru/ui/Base.hpp @@ -1,7 +1,7 @@ #pragma once #include "cru/common/Base.hpp" -#include "cru/platform/graph/Base.hpp" -#include "cru/platform/native/Base.hpp" +#include "cru/platform/graphics/Base.hpp" +#include "cru/platform/gui/Base.hpp" #include <functional> #include <memory> @@ -19,23 +19,35 @@ using cru::platform::RoundedRect; using cru::platform::Size; using cru::platform::TextRange; using cru::platform::Thickness; -using cru::platform::native::MouseButton; +using cru::platform::gui::MouseButton; -namespace mouse_buttons = cru::platform::native::mouse_buttons; +namespace mouse_buttons = cru::platform::gui::mouse_buttons; namespace colors = cru::platform::colors; //-------------------- region: forward declaration -------------------- + +namespace controls { class Window; class Control; -class ClickDetector; -class UiHost; +} // namespace controls + +namespace host { +class WindowHost; +} namespace render { class RenderObject; } +namespace style { +class StyleRuleSet; +class StyleRuleSetBind; +} // namespace style + //-------------------- region: basic types -------------------- +enum class Direction { Horizontal, Vertical }; + namespace internal { constexpr int align_start = 0; constexpr int align_end = align_start + 1; @@ -82,28 +94,20 @@ inline bool operator!=(const CornerRadius& left, const CornerRadius& right) { return !(left == right); } -struct BorderStyle { - std::shared_ptr<platform::graph::IBrush> border_brush; - Thickness border_thickness; - CornerRadius border_radius; - std::shared_ptr<platform::graph::IBrush> foreground_brush; - std::shared_ptr<platform::graph::IBrush> background_brush; -}; - class CanvasPaintEventArgs { public: - CanvasPaintEventArgs(platform::graph::IPainter* painter, + CanvasPaintEventArgs(platform::graphics::IPainter* painter, const Size& paint_size) : painter_(painter), paint_size_(paint_size) {} CRU_DEFAULT_COPY(CanvasPaintEventArgs) CRU_DEFAULT_MOVE(CanvasPaintEventArgs) ~CanvasPaintEventArgs() = default; - platform::graph::IPainter* GetPainter() const { return painter_; } + platform::graphics::IPainter* GetPainter() const { return painter_; } Size GetPaintSize() const { return paint_size_; } private: - platform::graph::IPainter* painter_; + platform::graphics::IPainter* painter_; Size paint_size_; }; diff --git a/include/cru/ui/ContentControl.hpp b/include/cru/ui/ContentControl.hpp deleted file mode 100644 index 19f13a1d..00000000 --- a/include/cru/ui/ContentControl.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include "Control.hpp" - -namespace cru::ui { -class ContentControl : public Control { - protected: - ContentControl(); - - public: - ContentControl(const ContentControl& other) = delete; - ContentControl(ContentControl&& other) = delete; - ContentControl& operator=(const ContentControl& other) = delete; - ContentControl& operator=(ContentControl&& other) = delete; - ~ContentControl() override; - - const std::vector<Control*>& GetChildren() const override final { - return child_vector_; - } - Control* GetChild() const { return child_; } - void SetChild(Control* child); - - protected: - virtual void OnChildChanged(Control* old_child, Control* new_child); - - private: - std::vector<Control*> child_vector_; - Control*& child_; -}; -} // namespace cru::ui diff --git a/include/cru/ui/DebugFlags.hpp b/include/cru/ui/DebugFlags.hpp new file mode 100644 index 00000000..51482135 --- /dev/null +++ b/include/cru/ui/DebugFlags.hpp @@ -0,0 +1,9 @@ +#pragma once + +namespace cru::ui::debug_flags { +constexpr bool routed_event = false; +constexpr bool layout = false; +constexpr bool shortcut = false; +constexpr bool text_service = false; +constexpr int click_detector = 0; +} // namespace cru::ui::debug_flags diff --git a/include/cru/ui/LayoutControl.hpp b/include/cru/ui/LayoutControl.hpp deleted file mode 100644 index 7997b37e..00000000 --- a/include/cru/ui/LayoutControl.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include "Control.hpp" - -namespace cru::ui { -class LayoutControl : public Control { - protected: - LayoutControl() = default; - - public: - LayoutControl(const LayoutControl& other) = delete; - LayoutControl(LayoutControl&& other) = delete; - LayoutControl& operator=(const LayoutControl& other) = delete; - LayoutControl& operator=(LayoutControl&& other) = delete; - ~LayoutControl() override; - - const std::vector<Control*>& GetChildren() const override final { - return children_; - } - - void AddChild(Control* control, Index position); - - void RemoveChild(Index position); - - protected: - virtual void OnAddChild(Control* child, Index position); - virtual void OnRemoveChild(Control* child, Index position); - - private: - std::vector<Control*> children_; -}; -} // namespace cru::ui diff --git a/include/cru/ui/UiHost.hpp b/include/cru/ui/UiHost.hpp deleted file mode 100644 index b1658ef6..00000000 --- a/include/cru/ui/UiHost.hpp +++ /dev/null @@ -1,172 +0,0 @@ -#pragma once -#include "Base.hpp" - -#include "cru/common/Event.hpp" -#include "cru/common/SelfResolvable.hpp" -#include "render/Base.hpp" - -namespace cru::ui { -struct AfterLayoutEventArgs {}; - -// The host of all controls and render objects. -// -// 3 situations on destroy: -// 1. Native window destroyed, IsRetainAfterDestroy: false: -// OnNativeDestroy(set native_window_destroyed_ to true, call ~Window due to -// deleting_ is false and IsRetainAfterDestroy is false) -> ~Window -> -// ~UiHost(not destroy native window repeatedly due to native_window_destroyed_ -// is true) -// 2. Native window destroyed, IsRetainAfterDestroy: true: -// OnNativeDestroy(set native_window_destroyed_ to true, not call ~Window -// because deleting_ is false and IsRetainAfterDestroy is true) -// then, ~Window -> ~UiHost(not destroy native window repeatedly due to -// native_window_destroyed_ is true) -// 3. Native window not destroyed, ~Window is called: -// ~Window -> ~UiHost(set deleting_ to true, destroy native window -// due to native_window_destroyed is false) -> OnNativeDestroy(not call ~Window -// due to deleting_ is true and IsRetainAfterDestroy is whatever) -// In conclusion: -// 1. Set native_window_destroyed_ to true at the beginning of OnNativeDestroy. -// 2. Set deleting_ to true at the beginning of ~UiHost. -// 3. Destroy native window when native_window_destroy_ is false in ~Window. -// 4. Delete Window when deleting_ is false and IsRetainAfterDestroy is false in -// OnNativeDestroy. -class UiHost : public Object, public SelfResolvable<UiHost> { - CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::UiHost") - - public: - // This will create root window render object and attach it to window. - // It will also create and manage a native window. - UiHost(Window* window); - - CRU_DELETE_COPY(UiHost) - CRU_DELETE_MOVE(UiHost) - - ~UiHost() override; - - public: - // Mark the layout as invalid, and arrange a re-layout later. - // This method could be called more than one times in a message cycle. But - // layout only takes place once. - void InvalidateLayout(); - - // Mark the paint as invalid, and arrange a re-paint later. - // This method could be called more than one times in a message cycle. But - // paint only takes place once. - void InvalidatePaint(); - - IEvent<AfterLayoutEventArgs>* AfterLayoutEvent() { - return &after_layout_event_; - } - - void Relayout(); - - // Get current control that mouse hovers on. This ignores the mouse-capture - // control. Even when mouse is captured by another control, this function - // return the control under cursor. You can use `GetMouseCaptureControl` to - // get more info. - Control* GetMouseHoverControl() const { return mouse_hover_control_; } - - //*************** region: focus *************** - - // Request focus for specified control. - bool RequestFocusFor(Control* control); - - // Get the control that has focus. - Control* GetFocusControl(); - - //*************** region: focus *************** - - // Pass nullptr to release capture. If mouse is already capture by a control, - // this capture will fail and return false. If control is identical to the - // capturing control, capture is not changed and this function will return - // true. - // - // When capturing control changes, - // appropriate event will be sent. If mouse is not on the capturing control - // and capture is released, mouse enter event will be sent to the mouse-hover - // control. If mouse is not on the capturing control and capture is set, mouse - // leave event will be sent to the mouse-hover control. - bool CaptureMouseFor(Control* control); - - // Return null if not captured. - Control* GetMouseCaptureControl(); - - Control* HitTest(const Point& point); - - void UpdateCursor(); - - std::shared_ptr<platform::native::INativeWindowResolver> - GetNativeWindowResolver() { - return native_window_resolver_; - } - - bool IsRetainAfterDestroy() { return retain_after_destroy_; } - - void SetRetainAfterDestroy(bool destroy) { retain_after_destroy_ = destroy; } - - private: - //*************** region: native messages *************** - void OnNativeDestroy(platform::native::INativeWindow* window, std::nullptr_t); - void OnNativePaint(platform::native::INativeWindow* window, std::nullptr_t); - void OnNativeResize(platform::native::INativeWindow* window, - const Size& size); - - void OnNativeFocus(platform::native::INativeWindow* window, - cru::platform::native::FocusChangeType focus); - - void OnNativeMouseEnterLeave( - platform::native::INativeWindow* window, - cru::platform::native::MouseEnterLeaveType enter); - void OnNativeMouseMove(platform::native::INativeWindow* window, - const Point& point); - void OnNativeMouseDown( - platform::native::INativeWindow* window, - const platform::native::NativeMouseButtonEventArgs& args); - void OnNativeMouseUp( - platform::native::INativeWindow* window, - const platform::native::NativeMouseButtonEventArgs& args); - - void OnNativeKeyDown(platform::native::INativeWindow* window, - const platform::native::NativeKeyEventArgs& args); - void OnNativeKeyUp(platform::native::INativeWindow* window, - const platform::native::NativeKeyEventArgs& args); - - //*************** region: event dispatcher helper *************** - - void DispatchMouseHoverControlChangeEvent(Control* old_control, - Control* new_control, - const Point& point, bool no_leave, - bool no_enter); - - private: - bool need_layout_ = false; - - Event<AfterLayoutEventArgs> after_layout_event_; - - std::shared_ptr<platform::native::INativeWindowResolver> - native_window_resolver_; - - // See remarks of UiHost. - bool retain_after_destroy_ = false; - // See remarks of UiHost. - bool deleting_ = false; - - // We need this because calling Resolve on resolver in handler of destroy - // event is bad and will always get the dying window. But we need to label the - // window as destroyed so the destructor will not destroy native window - // repeatedly. See remarks of UiHost. - bool native_window_destroyed_ = false; - - std::vector<EventRevokerGuard> event_revoker_guards_; - - Window* window_control_; - std::unique_ptr<render::WindowRenderObject> root_render_object_; - - Control* mouse_hover_control_; - - Control* focus_control_; // "focus_control_" can't be nullptr - - Control* mouse_captured_control_; -}; -} // namespace cru::ui diff --git a/include/cru/ui/UiManager.hpp b/include/cru/ui/UiManager.hpp index e6facdbd..6c0d9500 100644 --- a/include/cru/ui/UiManager.hpp +++ b/include/cru/ui/UiManager.hpp @@ -2,15 +2,22 @@ #include "Base.hpp" #include "controls/Base.hpp" +#include "style/StyleRuleSet.hpp" + +#include <memory> +#include <string> namespace cru::ui { struct ThemeResources { - std::shared_ptr<platform::graph::IFont> default_font; - std::shared_ptr<platform::graph::IBrush> text_brush; - std::shared_ptr<platform::graph::IBrush> text_selection_brush; - std::shared_ptr<platform::graph::IBrush> caret_brush; - controls::ButtonStyle button_style; - controls::TextBoxBorderStyle text_box_border_style; + std::u16string default_font_family; + std::shared_ptr<platform::graphics::IFont> default_font; + std::shared_ptr<platform::graphics::IBrush> text_brush; + std::shared_ptr<platform::graphics::IBrush> text_selection_brush; + std::shared_ptr<platform::graphics::IBrush> caret_brush; + style::StyleRuleSet button_style; + style::StyleRuleSet text_box_style; + + style::StyleRuleSet menu_item_style; }; class UiManager : public Object { diff --git a/include/cru/ui/Window.hpp b/include/cru/ui/Window.hpp deleted file mode 100644 index 450ea97b..00000000 --- a/include/cru/ui/Window.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once -#include "ContentControl.hpp" - -namespace cru::ui { -class Window final : public ContentControl { - friend UiHost; - - public: - static constexpr std::u16string_view control_type = u"Window"; - - public: - static Window* CreateOverlapped(); - - private: - struct tag_overlapped_constructor {}; - - explicit Window(tag_overlapped_constructor); - - public: - Window(const Window& other) = delete; - Window(Window&& other) = delete; - Window& operator=(const Window& other) = delete; - Window& operator=(Window&& other) = delete; - ~Window() override; - - public: - std::u16string_view GetControlType() const final; - - render::RenderObject* GetRenderObject() const override; - - protected: - void OnChildChanged(Control* old_child, Control* new_child) override; - - private: - std::unique_ptr<UiHost> managed_ui_host_; - - // UiHost is responsible to take care of lifetime of this. - render::WindowRenderObject* render_object_; -}; -} // namespace cru::ui diff --git a/include/cru/ui/components/Component.hpp b/include/cru/ui/components/Component.hpp new file mode 100644 index 00000000..0dfc587b --- /dev/null +++ b/include/cru/ui/components/Component.hpp @@ -0,0 +1,19 @@ +#pragma once +#include "../Base.hpp" + +namespace cru::ui::components { +// In destructor, component should check all owned controls whether it is +// attached to window, if not, destroy them, otherwise it is host's duty to +// destroy them. +class Component : public Object { + public: + Component() = default; + + CRU_DELETE_COPY(Component) + CRU_DELETE_MOVE(Component) + + ~Component() = default; + + virtual controls::Control* GetRootControl() = 0; +}; +} // namespace cru::ui::components diff --git a/include/cru/ui/components/Menu.hpp b/include/cru/ui/components/Menu.hpp new file mode 100644 index 00000000..dedf2bd5 --- /dev/null +++ b/include/cru/ui/components/Menu.hpp @@ -0,0 +1,60 @@ +#pragma once +#include "Component.hpp" +#include "cru/common/Base.hpp" +#include "cru/ui/controls/Button.hpp" +#include "cru/ui/controls/Control.hpp" +#include "cru/ui/controls/FlexLayout.hpp" +#include "cru/ui/controls/TextBlock.hpp" + +#include <string> +#include <vector> + +namespace cru::ui::components { +class MenuItem : public Component { + public: + MenuItem(); + explicit MenuItem(std::u16string text); + + CRU_DELETE_COPY(MenuItem) + CRU_DELETE_MOVE(MenuItem) + + ~MenuItem(); + + public: + controls::Control* GetRootControl() override { return container_; } + + void SetText(std::u16string text); + + private: + controls::Button* container_; + controls::TextBlock* text_; +}; + +class Menu : public Component { + public: + Menu(); + + CRU_DELETE_COPY(Menu) + CRU_DELETE_MOVE(Menu) + + ~Menu(); + + public: + gsl::index GetItemCount() const { + return static_cast<gsl::index>(items_.size()); + } + + void AddItem(Component* component) { AddItem(component, GetItemCount()); } + void AddItem(Component* component, gsl::index index); + Component* RemoveItem(gsl::index index); + + void AddTextItem(std::u16string text) { + AddTextItem(std::move(text), GetItemCount()); + } + void AddTextItem(std::u16string text, gsl::index index); + + private: + controls::FlexLayout* container_; + std::vector<Component*> items_; +}; +} // namespace cru::ui::components diff --git a/include/cru/ui/controls/Base.hpp b/include/cru/ui/controls/Base.hpp index b550601b..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 = 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 { - BorderStyle normal; - BorderStyle hover; - BorderStyle focus; - 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 a4f727d6..1c9b1216 100644 --- a/include/cru/ui/controls/Button.hpp +++ b/include/cru/ui/controls/Button.hpp @@ -1,11 +1,16 @@ #pragma once -#include "../ContentControl.hpp" -#include "Base.hpp" +#include "ContentControl.hpp" -#include "../ClickDetector.hpp" +#include "../helper/ClickDetector.hpp" +#include "IBorderControl.hpp" +#include "IClickableControl.hpp" +#include "cru/common/Event.hpp" +#include "cru/ui/style/ApplyBorderStyleInfo.hpp" namespace cru::ui::controls { -class Button : public ContentControl { +class Button : public ContentControl, + public virtual IClickableControl, + public virtual IBorderControl { public: static constexpr std::u16string_view control_type = u"Button"; @@ -26,17 +31,19 @@ class Button : public ContentControl { render::RenderObject* GetRenderObject() const override; public: - const ButtonStyle& GetStyle() const { return style_; } - void SetStyle(ButtonStyle style); + helper::ClickState GetClickState() override { + return click_detector_.GetState(); + } - protected: - void OnChildChanged(Control* old_child, Control* new_child) override; + IEvent<helper::ClickState>* ClickStateChangeEvent() override { + return click_detector_.StateChangeEvent(); + } + + void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) override; private: std::unique_ptr<render::BorderRenderObject> render_object_{}; - ButtonStyle style_; - - ClickDetector click_detector_; + helper::ClickDetector click_detector_; }; } // namespace cru::ui::controls diff --git a/include/cru/ui/controls/Container.hpp b/include/cru/ui/controls/Container.hpp index 304d402c..18958837 100644 --- a/include/cru/ui/controls/Container.hpp +++ b/include/cru/ui/controls/Container.hpp @@ -1,5 +1,5 @@ #pragma once -#include "../ContentControl.hpp" +#include "ContentControl.hpp" namespace cru::ui::controls { class Container : public ContentControl { @@ -19,9 +19,6 @@ class Container : public ContentControl { render::RenderObject* GetRenderObject() const override; - protected: - void OnChildChanged(Control* old_child, Control* new_child) override; - private: std::unique_ptr<render::BorderRenderObject> render_object_; }; diff --git a/include/cru/ui/controls/ContentControl.hpp b/include/cru/ui/controls/ContentControl.hpp new file mode 100644 index 00000000..1bdaf7e4 --- /dev/null +++ b/include/cru/ui/controls/ContentControl.hpp @@ -0,0 +1,38 @@ +#pragma once +#include "Control.hpp" + +#include "cru/ui/render/RenderObject.hpp" + +namespace cru::ui::controls { +class ContentControl : public Control { + protected: + ContentControl() = default; + + public: + ContentControl(const ContentControl& other) = delete; + ContentControl(ContentControl&& other) = delete; + ContentControl& operator=(const ContentControl& other) = delete; + ContentControl& operator=(ContentControl&& other) = delete; + ~ContentControl() override = default; + + Control* GetChild() const; + void SetChild(Control* child); + + protected: + virtual void OnChildChanged(Control* old_child, Control* new_child); + + render::RenderObject* GetContainerRenderObject() const { + return container_render_object_; + } + void SetContainerRenderObject(render::RenderObject* ro) { + container_render_object_ = ro; + } + + private: + using Control::AddChild; + using Control::RemoveChild; + + private: + render::RenderObject* container_render_object_ = nullptr; +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/Control.hpp b/include/cru/ui/controls/Control.hpp index bd86bc2f..341b6ef2 100644 --- a/include/cru/ui/Control.hpp +++ b/include/cru/ui/controls/Control.hpp @@ -1,15 +1,15 @@ #pragma once #include "Base.hpp" +#include "../events/UiEvent.hpp" +#include "../render/Base.hpp" #include "cru/common/Event.hpp" -#include "render/Base.hpp" -#include "UiEvent.hpp" #include <string_view> -namespace cru::ui { +namespace cru::ui::controls { class Control : public Object { - friend UiHost; + friend host::WindowHost; protected: Control(); @@ -19,39 +19,31 @@ class Control : public Object { Control(Control&& other) = delete; Control& operator=(const Control& other) = delete; Control& operator=(Control&& other) = delete; - ~Control() override = default; + ~Control() override; public: virtual std::u16string_view GetControlType() const = 0; //*************** region: tree *************** public: - // Get the ui host if attached, otherwise, return nullptr. - UiHost* GetUiHost() const { return ui_host_; } + host::WindowHost* GetWindowHost() const; Control* GetParent() const { return parent_; } - virtual const std::vector<Control*>& GetChildren() const = 0; + const std::vector<Control*>& GetChildren() const { return children_; } // Traverse the tree rooted the control including itself. void TraverseDescendants(const std::function<void(Control*)>& predicate); - void _SetParent(Control* parent); - void _SetDescendantUiHost(UiHost* host); - - private: - static void _TraverseDescendants( - Control* control, const std::function<void(Control*)>& predicate); - public: virtual render::RenderObject* GetRenderObject() const = 0; //*************** region: focus *************** public: - bool RequestFocus(); - bool HasFocus(); + void SetFocus(); + //*************** region: mouse *************** public: bool IsMouseOver() const { return is_mouse_over_; } @@ -66,13 +58,16 @@ class Control : public Object { // Cursor is inherited from parent recursively if not set. public: // null for not set - std::shared_ptr<platform::native::ICursor> GetCursor(); + std::shared_ptr<platform::gui::ICursor> GetCursor(); // will not return nullptr - std::shared_ptr<platform::native::ICursor> GetInheritedCursor(); + std::shared_ptr<platform::gui::ICursor> GetInheritedCursor(); // null to unset - void SetCursor(std::shared_ptr<platform::native::ICursor> cursor); + void SetCursor(std::shared_ptr<platform::gui::ICursor> cursor); + + public: + style::StyleRuleSet* GetStyleRuleSet(); //*************** region: events *************** public: @@ -134,19 +129,29 @@ class Control : public Object { //*************** region: tree *************** protected: + void AddChild(Control* control, Index position); + void RemoveChild(Index position); + virtual void OnAddChild(Control* child, Index position); + virtual void OnRemoveChild(Control* child, Index position); virtual void OnParentChanged(Control* old_parent, Control* new_parent); - virtual void OnAttachToHost(UiHost* host); - virtual void OnDetachFromHost(UiHost* host); + virtual void OnAttachToHost(host::WindowHost* host); + virtual void OnDetachFromHost(host::WindowHost* host); + protected: virtual void OnMouseHoverChange(bool newHover) { CRU_UNUSED(newHover) } private: - UiHost* ui_host_ = nullptr; Control* parent_ = nullptr; + std::vector<Control*> children_; + + host::WindowHost* window_host_ = nullptr; private: bool is_mouse_over_ = false; - std::shared_ptr<platform::native::ICursor> cursor_ = nullptr; + std::shared_ptr<platform::gui::ICursor> cursor_ = nullptr; + + std::unique_ptr<style::StyleRuleSet> style_rule_set_; + std::unique_ptr<style::StyleRuleSetBind> style_rule_set_bind_; }; -} // namespace cru::ui +} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/FlexLayout.hpp b/include/cru/ui/controls/FlexLayout.hpp index 87162569..4f6abfdb 100644 --- a/include/cru/ui/controls/FlexLayout.hpp +++ b/include/cru/ui/controls/FlexLayout.hpp @@ -1,5 +1,5 @@ #pragma once -#include "../LayoutControl.hpp" +#include "LayoutControl.hpp" namespace cru::ui::controls { class FlexLayout : public LayoutControl { @@ -28,13 +28,12 @@ class FlexLayout : public LayoutControl { FlexDirection GetFlexDirection() const; void SetFlexDirection(FlexDirection direction); + FlexCrossAlignment GetItemCrossAlign() const; + void SetItemCrossAlign(FlexCrossAlignment alignment); + FlexChildLayoutData GetChildLayoutData(Control* control); void SetChildLayoutData(Control* control, FlexChildLayoutData data); - protected: - void OnAddChild(Control* child, Index position) override; - void OnRemoveChild(Control* child, Index position) override; - private: std::shared_ptr<render::FlexLayoutRenderObject> render_object_; }; diff --git a/include/cru/ui/controls/IBorderControl.hpp b/include/cru/ui/controls/IBorderControl.hpp new file mode 100644 index 00000000..817305ef --- /dev/null +++ b/include/cru/ui/controls/IBorderControl.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "../style/ApplyBorderStyleInfo.hpp" +#include "Base.hpp" +#include "cru/common/Base.hpp" + +namespace cru::ui::controls { +struct IBorderControl : virtual Interface { + virtual void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) = 0; +}; +} // namespace cru::ui::controls 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<helper::ClickState>* ClickStateChangeEvent() = 0; +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/LayoutControl.hpp b/include/cru/ui/controls/LayoutControl.hpp new file mode 100644 index 00000000..106dd94d --- /dev/null +++ b/include/cru/ui/controls/LayoutControl.hpp @@ -0,0 +1,35 @@ +#pragma once +#include "Control.hpp" + +namespace cru::ui::controls { +class LayoutControl : public Control { + protected: + LayoutControl() = default; + explicit LayoutControl(render::RenderObject* container_render_object) + : container_render_object_(container_render_object) {} + + public: + LayoutControl(const LayoutControl& other) = delete; + LayoutControl(LayoutControl&& other) = delete; + LayoutControl& operator=(const LayoutControl& other) = delete; + LayoutControl& operator=(LayoutControl&& other) = delete; + ~LayoutControl() override = default; + + using Control::AddChild; + using Control::RemoveChild; + + protected: + // If container render object is not null. Render object of added or removed + // child control will automatically sync to the container render object. + render::RenderObject* GetContainerRenderObject() const; + void SetContainerRenderObject(render::RenderObject* ro) { + container_render_object_ = ro; + } + + void OnAddChild(Control* child, Index position) override; + void OnRemoveChild(Control* child, Index position) override; + + private: + render::RenderObject* container_render_object_ = nullptr; +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/NoChildControl.hpp b/include/cru/ui/controls/NoChildControl.hpp index 1a31ae7e..562137f1 100644 --- a/include/cru/ui/NoChildControl.hpp +++ b/include/cru/ui/controls/NoChildControl.hpp @@ -1,11 +1,8 @@ #pragma once #include "Control.hpp" -namespace cru::ui { +namespace cru::ui::controls { class NoChildControl : public Control { - private: - static const std::vector<Control*> empty_control_vector; - protected: NoChildControl() = default; @@ -16,9 +13,8 @@ class NoChildControl : public Control { NoChildControl& operator=(NoChildControl&& other) = delete; ~NoChildControl() override = default; - protected: - const std::vector<Control*>& GetChildren() const override final { - return empty_control_vector; - } + private: + using Control::AddChild; + using Control::RemoveChild; }; } // namespace cru::ui diff --git a/include/cru/ui/controls/Popup.hpp b/include/cru/ui/controls/Popup.hpp new file mode 100644 index 00000000..d76e1211 --- /dev/null +++ b/include/cru/ui/controls/Popup.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "RootControl.hpp" + +#include "cru/ui/Base.hpp" +#include "cru/platform/gui/Base.hpp" + +#include <memory> + +namespace cru::ui::controls { +class Popup : public RootControl { + public: + explicit Popup(Control* attached_control = nullptr); + + CRU_DELETE_COPY(Popup) + CRU_DELETE_MOVE(Popup) + + ~Popup() override; + + protected: + gsl::not_null<platform::gui::INativeWindow*> CreateNativeWindow( + gsl::not_null<host::WindowHost*> host, + platform::gui::INativeWindow* parent) override; +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/RootControl.hpp b/include/cru/ui/controls/RootControl.hpp new file mode 100644 index 00000000..53e69e7c --- /dev/null +++ b/include/cru/ui/controls/RootControl.hpp @@ -0,0 +1,45 @@ +#pragma once +#include "LayoutControl.hpp" + +#include "cru/common/Base.hpp" +#include "cru/platform/gui/Base.hpp" +#include "cru/ui/Base.hpp" + +namespace cru::ui::controls { +class RootControl : public LayoutControl { + protected: + explicit RootControl(Control* attached_control); + + public: + CRU_DELETE_COPY(RootControl) + CRU_DELETE_MOVE(RootControl) + ~RootControl() override; + + public: + render::RenderObject* GetRenderObject() const override; + + void EnsureWindowCreated(); + + // If create is false and native window is not create, it will not be created + // and shown. + void Show(bool create = true); + + Rect GetRect(); + void SetRect(const Rect& rect); + + protected: + virtual gsl::not_null<platform::gui::INativeWindow*> CreateNativeWindow( + gsl::not_null<host::WindowHost*> host, + platform::gui::INativeWindow* parent) = 0; + + private: + platform::gui::INativeWindow* GetNativeWindow(bool create); + + private: + std::unique_ptr<host::WindowHost> window_host_; + + std::unique_ptr<render::StackLayoutRenderObject> render_object_; + + Control* attached_control_; +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/StackLayout.hpp b/include/cru/ui/controls/StackLayout.hpp index c0b95044..aa9440c2 100644 --- a/include/cru/ui/controls/StackLayout.hpp +++ b/include/cru/ui/controls/StackLayout.hpp @@ -1,5 +1,5 @@ #pragma once -#include "../LayoutControl.hpp" +#include "LayoutControl.hpp" namespace cru::ui::controls { class StackLayout : public LayoutControl { @@ -21,10 +21,6 @@ class StackLayout : public LayoutControl { render::RenderObject* GetRenderObject() const override; - protected: - void OnAddChild(Control* child, Index position) override; - void OnRemoveChild(Control* child, Index position) override; - private: std::shared_ptr<render::StackLayoutRenderObject> render_object_; }; diff --git a/include/cru/ui/controls/TextBlock.hpp b/include/cru/ui/controls/TextBlock.hpp index 8a9a3bff..be31816c 100644 --- a/include/cru/ui/controls/TextBlock.hpp +++ b/include/cru/ui/controls/TextBlock.hpp @@ -1,15 +1,15 @@ #pragma once -#include "../NoChildControl.hpp" +#include "NoChildControl.hpp" -namespace cru::ui::controls { -template <typename TControl> -class TextControlService; +#include "TextHostControlService.hpp" -class TextBlock : public NoChildControl { +namespace cru::ui::controls { +class TextBlock : public NoChildControl, public virtual ITextHostControl { public: static constexpr std::u16string_view control_type = u"TextBlock"; - static TextBlock* Create() { return new TextBlock(); } + static TextBlock* Create(); + static TextBlock* Create(std::u16string text, bool selectable = false); protected: TextBlock(); @@ -28,12 +28,17 @@ class TextBlock : public NoChildControl { std::u16string GetText() const; void SetText(std::u16string text); - gsl::not_null<render::TextRenderObject*> GetTextRenderObject(); - render::ScrollRenderObject* GetScrollRenderObject() { return nullptr; } + bool IsSelectable() const; + void SetSelectable(bool value); + + gsl::not_null<render::TextRenderObject*> GetTextRenderObject() override; + render::ScrollRenderObject* GetScrollRenderObject() override { + return nullptr; + } private: std::unique_ptr<render::TextRenderObject> text_render_object_; - std::unique_ptr<TextControlService<TextBlock>> service_; + std::unique_ptr<TextHostControlService> service_; }; } // namespace cru::ui::controls diff --git a/include/cru/ui/controls/TextBox.hpp b/include/cru/ui/controls/TextBox.hpp index 5976f6da..5693b315 100644 --- a/include/cru/ui/controls/TextBox.hpp +++ b/include/cru/ui/controls/TextBox.hpp @@ -1,6 +1,8 @@ #pragma once -#include "../NoChildControl.hpp" -#include "Base.hpp" +#include "NoChildControl.hpp" + +#include "IBorderControl.hpp" +#include "TextHostControlService.hpp" #include <memory> @@ -8,7 +10,9 @@ namespace cru::ui::controls { template <typename TControl> class TextControlService; -class TextBox : public NoChildControl { +class TextBox : public NoChildControl, + public virtual IBorderControl, + public virtual ITextHostControl { public: static constexpr std::u16string_view control_type = u"TextBox"; @@ -27,25 +31,16 @@ class TextBox : public NoChildControl { render::RenderObject* GetRenderObject() const override; - gsl::not_null<render::TextRenderObject*> GetTextRenderObject(); - render::ScrollRenderObject* GetScrollRenderObject(); - - const TextBoxBorderStyle& GetBorderStyle(); - void SetBorderStyle(TextBoxBorderStyle border_style); - - protected: - void OnMouseHoverChange(bool newHover) override; + gsl::not_null<render::TextRenderObject*> GetTextRenderObject() override; + render::ScrollRenderObject* GetScrollRenderObject() override; - private: - void UpdateBorderStyle(); + void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) override; private: std::unique_ptr<render::BorderRenderObject> border_render_object_; std::unique_ptr<render::ScrollRenderObject> scroll_render_object_; std::unique_ptr<render::TextRenderObject> text_render_object_; - TextBoxBorderStyle border_style_; - - std::unique_ptr<TextControlService<TextBox>> service_; + std::unique_ptr<TextHostControlService> service_; }; } // namespace cru::ui::controls diff --git a/include/cru/ui/controls/TextHostControlService.hpp b/include/cru/ui/controls/TextHostControlService.hpp new file mode 100644 index 00000000..340228fe --- /dev/null +++ b/include/cru/ui/controls/TextHostControlService.hpp @@ -0,0 +1,137 @@ +#pragma once +#include "Base.hpp" + +#include "cru/platform/gui/InputMethod.hpp" +#include "cru/platform/gui/UiApplication.hpp" +#include "cru/ui/controls/Control.hpp" +#include "cru/ui/helper/ShortcutHub.hpp" + +#include <functional> +#include <string> + +namespace cru::ui::render { +class TextRenderObject; +class ScrollRenderObject; +} // namespace cru::ui::render + +namespace cru::ui::controls { +constexpr int k_default_caret_blink_duration = 500; + +struct ITextHostControl : virtual Interface { + virtual gsl::not_null<render::TextRenderObject*> GetTextRenderObject() = 0; + // May return nullptr. + virtual render::ScrollRenderObject* GetScrollRenderObject() = 0; +}; + +class TextHostControlService : public Object { + CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::controls::TextControlService") + + public: + TextHostControlService(gsl::not_null<Control*> control); + + CRU_DELETE_COPY(TextHostControlService) + CRU_DELETE_MOVE(TextHostControlService) + + ~TextHostControlService() = default; + + public: + bool IsEnabled() { return enable_; } + void SetEnabled(bool enable); + + bool IsEditable() { return this->editable_; } + void SetEditable(bool editable); + + std::u16string GetText() { return this->text_; } + std::u16string_view GetTextView() { return this->text_; } + void SetText(std::u16string text, bool stop_composition = false); + + void InsertText(gsl::index position, std::u16string_view text, + bool stop_composition = false); + void DeleteChar(gsl::index position, bool stop_composition = false); + + // Return the position of deleted character. + gsl::index DeleteCharPrevious(gsl::index position, + bool stop_composition = false); + void DeleteText(TextRange range, bool stop_composition = false); + + void CancelComposition(); + + std::optional<platform::gui::CompositionText> GetCompositionInfo(); + + bool IsCaretVisible() { return caret_visible_; } + void SetCaretVisible(bool visible); + + int GetCaretBlinkDuration() { return caret_blink_duration_; } + void SetCaretBlinkDuration(int milliseconds); + + gsl::index GetCaretPosition() { return selection_.GetEnd(); } + TextRange GetSelection() { return selection_; } + + void SetSelection(gsl::index caret_position); + void SetSelection(TextRange selection, bool scroll_to_caret = true); + + void ChangeSelectionEnd(gsl::index new_end); + void AbortSelection(); + + void DeleteSelectedText(); + // If some text is selected, then they are deleted first. Then insert text + // into caret position. + void ReplaceSelectedText(std::u16string_view text); + + void ScrollToCaret(); + + private: + gsl::not_null<render::TextRenderObject*> GetTextRenderObject(); + render::ScrollRenderObject* GetScrollRenderObject(); + + // May return nullptr. + platform::gui::IInputMethodContext* GetInputMethodContext(); + + void CoerceSelection(); + + void SetupCaret(); + void TearDownCaret(); + + void SyncTextRenderObject(); + + void UpdateInputMethodPosition(); + + template <typename TArgs> + void SetupOneHandler(event::RoutedEvent<TArgs>* (Control::*event)(), + void (TextHostControlService::*handler)( + typename event::RoutedEvent<TArgs>::EventArgs)) { + this->event_guard_ += (this->control_->*event)()->Bubble()->AddHandler( + std::bind(handler, this, std::placeholders::_1)); + } + + void MouseMoveHandler(event::MouseEventArgs& args); + void MouseDownHandler(event::MouseButtonEventArgs& args); + void MouseUpHandler(event::MouseButtonEventArgs& args); + void GainFocusHandler(event::FocusChangeEventArgs& args); + void LoseFocusHandler(event::FocusChangeEventArgs& args); + + void SetUpShortcuts(); + + private: + gsl::not_null<Control*> control_; + gsl::not_null<ITextHostControl*> text_host_control_; + + EventRevokerListGuard event_guard_; + EventRevokerListGuard input_method_context_event_guard_; + + std::u16string text_; + TextRange selection_; + + bool enable_ = false; + bool editable_ = false; + + bool caret_visible_ = false; + platform::gui::TimerAutoCanceler caret_timer_canceler_; + int caret_blink_duration_ = k_default_caret_blink_duration; + + helper::ShortcutHub shortcut_hub_; + + // true if left mouse is down and selecting + bool mouse_move_selecting_ = false; +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/Window.hpp b/include/cru/ui/controls/Window.hpp new file mode 100644 index 00000000..cca56b64 --- /dev/null +++ b/include/cru/ui/controls/Window.hpp @@ -0,0 +1,32 @@ +#pragma once +#include "cru/platform/gui/Base.hpp" +#include "cru/ui/controls/RootControl.hpp" + +#include "cru/common/Base.hpp" + +namespace cru::ui::controls { +class Window final : public RootControl { + public: + static constexpr std::u16string_view control_type = u"Window"; + + public: + static Window* Create(Control* attached_control = nullptr); + + private: + explicit Window(Control* attached_control); + + public: + CRU_DELETE_COPY(Window) + CRU_DELETE_MOVE(Window) + + ~Window() override; + + public: + std::u16string_view GetControlType() const final { return control_type; } + + protected: + gsl::not_null<platform::gui::INativeWindow*> CreateNativeWindow( + gsl::not_null<host::WindowHost*> host, + platform::gui::INativeWindow* parent) override; +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/UiEvent.hpp b/include/cru/ui/events/UiEvent.hpp index 5adace8a..22ad0150 100644 --- a/include/cru/ui/UiEvent.hpp +++ b/include/cru/ui/events/UiEvent.hpp @@ -1,15 +1,15 @@ #pragma once -#include "Base.hpp" +#include "../Base.hpp" #include "cru/common/Event.hpp" -#include "cru/platform/native/Keyboard.hpp" +#include "cru/platform/gui/Keyboard.hpp" #include <memory> #include <optional> #include <string> #include <type_traits> -namespace cru::platform::graph { +namespace cru::platform::graphics { struct IPainter; } @@ -84,6 +84,7 @@ class MouseEventArgs : public UiEventArgs { // This point is relative to window client lefttop. Point GetPoint() const { return point_.value_or(Point{}); } + Point GetPoint(render::RenderObject* render_target) const; Point GetPointToContent(render::RenderObject* render_target) const; private: @@ -94,13 +95,13 @@ class MouseButtonEventArgs : public MouseEventArgs { public: MouseButtonEventArgs(Object* sender, Object* original_sender, const Point& point, const MouseButton button, - platform::native::KeyModifier key_modifier) + platform::gui::KeyModifier key_modifier) : MouseEventArgs(sender, original_sender, point), button_(button), key_modifier_(key_modifier) {} MouseButtonEventArgs(Object* sender, Object* original_sender, const MouseButton button, - platform::native::KeyModifier key_modifier) + platform::gui::KeyModifier key_modifier) : MouseEventArgs(sender, original_sender), button_(button), key_modifier_(key_modifier) {} @@ -111,11 +112,11 @@ class MouseButtonEventArgs : public MouseEventArgs { ~MouseButtonEventArgs() override = default; MouseButton GetButton() const { return button_; } - platform::native::KeyModifier GetKeyModifier() const { return key_modifier_; } + platform::gui::KeyModifier GetKeyModifier() const { return key_modifier_; } private: MouseButton button_; - platform::native::KeyModifier key_modifier_; + platform::gui::KeyModifier key_modifier_; }; class MouseWheelEventArgs : public MouseEventArgs { @@ -138,7 +139,7 @@ class MouseWheelEventArgs : public MouseEventArgs { class PaintEventArgs : public UiEventArgs { public: PaintEventArgs(Object* sender, Object* original_sender, - platform::graph::IPainter* painter) + platform::graphics::IPainter* painter) : UiEventArgs(sender, original_sender), painter_(painter) {} PaintEventArgs(const PaintEventArgs& other) = default; PaintEventArgs(PaintEventArgs&& other) = default; @@ -146,10 +147,10 @@ class PaintEventArgs : public UiEventArgs { PaintEventArgs& operator=(PaintEventArgs&& other) = default; ~PaintEventArgs() = default; - platform::graph::IPainter* GetPainter() const { return painter_; } + platform::graphics::IPainter* GetPainter() const { return painter_; } private: - platform::graph::IPainter* painter_; + platform::graphics::IPainter* painter_; }; class FocusChangeEventArgs : public UiEventArgs { @@ -191,8 +192,8 @@ class ToggleEventArgs : public UiEventArgs { class KeyEventArgs : public UiEventArgs { public: KeyEventArgs(Object* sender, Object* original_sender, - platform::native::KeyCode key_code, - platform::native::KeyModifier key_modifier) + platform::gui::KeyCode key_code, + platform::gui::KeyModifier key_modifier) : UiEventArgs(sender, original_sender), key_code_(key_code), key_modifier_(key_modifier) {} @@ -202,12 +203,12 @@ class KeyEventArgs : public UiEventArgs { KeyEventArgs& operator=(KeyEventArgs&& other) = default; ~KeyEventArgs() override = default; - platform::native::KeyCode GetKeyCode() const { return key_code_; } - platform::native::KeyModifier GetKeyModifier() const { return key_modifier_; } + platform::gui::KeyCode GetKeyCode() const { return key_code_; } + platform::gui::KeyModifier GetKeyModifier() const { return key_modifier_; } private: - platform::native::KeyCode key_code_; - platform::native::KeyModifier key_modifier_; + platform::gui::KeyCode key_code_; + platform::gui::KeyModifier key_modifier_; }; class CharEventArgs : public UiEventArgs { diff --git a/include/cru/ui/ClickDetector.hpp b/include/cru/ui/helper/ClickDetector.hpp index 4ffe5d05..b58297b1 100644 --- a/include/cru/ui/ClickDetector.hpp +++ b/include/cru/ui/helper/ClickDetector.hpp @@ -1,10 +1,10 @@ #pragma once -#include "Control.hpp" +#include "../controls/Control.hpp" -namespace cru::ui { +namespace cru::ui::helper { class ClickEventArgs : Object { public: - ClickEventArgs(Control* sender, const Point& down_point, + ClickEventArgs(controls::Control* sender, const Point& down_point, const Point& up_point, MouseButton button) : sender_(sender), down_point_(down_point), @@ -16,13 +16,13 @@ class ClickEventArgs : Object { ~ClickEventArgs() override = default; - Control* GetSender() const { return sender_; } + controls::Control* GetSender() const { return sender_; } Point GetDownPoint() const { return down_point_; } Point GetUpPoint() const { return up_point_; } MouseButton GetButton() const { return button_; } private: - Control* sender_; + controls::Control* sender_; Point down_point_; Point up_point_; MouseButton button_; @@ -39,14 +39,14 @@ class ClickDetector : public Object { CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::ClickDetector") public: - explicit ClickDetector(Control* control); + explicit ClickDetector(controls::Control* control); CRU_DELETE_COPY(ClickDetector) CRU_DELETE_MOVE(ClickDetector) ~ClickDetector() override = default; - Control* GetControl() const { return control_; } + controls::Control* GetControl() const { return control_; } ClickState GetState() const { return state_; } @@ -69,9 +69,9 @@ class ClickDetector : public Object { void SetState(ClickState state); private: - Control* control_; + controls::Control* control_; - ClickState state_; + ClickState state_ = ClickState::None; bool enable_ = true; MouseButton trigger_button_ = mouse_buttons::left | mouse_buttons::right; @@ -84,4 +84,4 @@ class ClickDetector : public Object { Point down_point_; MouseButton button_; }; -} // namespace cru::ui +} // namespace cru::ui::helper diff --git a/include/cru/ui/helper/ShortcutHub.hpp b/include/cru/ui/helper/ShortcutHub.hpp new file mode 100644 index 00000000..fe3414fe --- /dev/null +++ b/include/cru/ui/helper/ShortcutHub.hpp @@ -0,0 +1,134 @@ +#pragma once +#include "../Base.hpp" + +#include "../events/UiEvent.hpp" +#include "cru/common/Event.hpp" +#include "cru/platform/gui/Keyboard.hpp" + +#include <cstddef> +#include <functional> +#include <memory> +#include <optional> +#include <string> +#include <string_view> +#include <type_traits> +#include <unordered_map> +#include <vector> + +namespace cru::ui::helper { + +class ShortcutKeyBind { + public: + ShortcutKeyBind( + platform::gui::KeyCode key, + platform::gui::KeyModifier modifier = platform::gui::KeyModifiers::none) + : key_(key), modifier_(modifier) {} + + CRU_DEFAULT_COPY(ShortcutKeyBind) + CRU_DEFAULT_MOVE(ShortcutKeyBind) + + ~ShortcutKeyBind() = default; + + platform::gui::KeyCode GetKey() const { return key_; } + platform::gui::KeyModifier GetModifier() const { return modifier_; } + + bool Is(platform::gui::KeyCode key, + platform::gui::KeyModifier modifier) const { + return key == key_ && modifier == modifier_; + } + + bool operator==(const ShortcutKeyBind& other) const { + return this->key_ == other.key_ && this->modifier_ == other.modifier_; + } + + bool operator!=(const ShortcutKeyBind& other) const { + return !this->operator==(other); + } + + std::u16string ToString() { + std::u16string result = u"("; + result += platform::gui::ToString(modifier_); + result += u")"; + result += platform::gui::ToString(key_); + return result; + } + + private: + platform::gui::KeyCode key_; + platform::gui::KeyModifier modifier_; +}; +} // namespace cru::ui::helper + +namespace std { +template <> +struct hash<cru::ui::helper::ShortcutKeyBind> { + std::size_t operator()(const cru::ui::helper::ShortcutKeyBind& value) const { + std::size_t result = 0; + cru::hash_combine(result, static_cast<int>(value.GetKey())); + cru::hash_combine(result, static_cast<int>(value.GetModifier())); + return result; + } +}; +} // namespace std + +namespace cru::ui::helper { +struct Shortcut { + // Just for debug. + std::u16string name; + ShortcutKeyBind key_bind; + // Return true if it consumes the shortcut. Or return false if it does not + // handle the shortcut. + std::function<bool()> handler; +}; + +struct ShortcutInfo { + int id; + std::u16string name; + ShortcutKeyBind key_bind; + std::function<bool()> handler; +}; + +class ShortcutHub : public Object { + public: + ShortcutHub() = default; + + CRU_DELETE_COPY(ShortcutHub) + CRU_DELETE_MOVE(ShortcutHub) + + ~ShortcutHub() override = default; + + int RegisterShortcut(std::u16string name, ShortcutKeyBind bind, + std::function<bool()> handler) { + return RegisterShortcut({std::move(name), bind, std::move(handler)}); + } + + // Return an id used for unregistering. + int RegisterShortcut(Shortcut shortcut); + + void UnregisterShortcut(int id); + + std::vector<ShortcutInfo> GetAllShortcuts() const; + std::optional<ShortcutInfo> GetShortcut(int id) const; + const std::vector<ShortcutInfo>& GetShortcutByKeyBind( + const ShortcutKeyBind& key_bind) const; + + IEvent<event::KeyEventArgs&>* FallbackKeyEvent() { return &fallback_event_; } + + void Install(controls::Control* control); + void Uninstall(); + + private: + void OnKeyDown(event::KeyEventArgs& event); + + private: + std::unordered_map<ShortcutKeyBind, std::vector<ShortcutInfo>> map_; + + const std::vector<ShortcutInfo> empty_list_; + + int current_id_ = 1; + + Event<event::KeyEventArgs&> fallback_event_; + + EventRevokerListGuard event_guard_; +}; +} // namespace cru::ui::helper diff --git a/include/cru/ui/host/LayoutPaintCycler.hpp b/include/cru/ui/host/LayoutPaintCycler.hpp new file mode 100644 index 00000000..ed543afa --- /dev/null +++ b/include/cru/ui/host/LayoutPaintCycler.hpp @@ -0,0 +1,39 @@ +#pragma once +#include "../Base.hpp" + +#include "cru/platform/gui/UiApplication.hpp" + +#include <chrono> + +namespace cru::ui::host { +class LayoutPaintCycler { + public: + explicit LayoutPaintCycler(WindowHost* host); + + CRU_DELETE_COPY(LayoutPaintCycler) + CRU_DELETE_MOVE(LayoutPaintCycler) + + ~LayoutPaintCycler(); + + public: + void InvalidateLayout(); + void InvalidatePaint(); + + bool IsLayoutDirty() { return layout_dirty_; } + + private: + void OnCycle(); + + private: + WindowHost* host_; + + platform::gui::TimerAutoCanceler timer_canceler_; + + bool layout_dirty_ = true; + bool paint_dirty_ = true; + + std::chrono::steady_clock::time_point last_cycle_time_; + std::chrono::steady_clock::duration cycle_threshold_ = + std::chrono::milliseconds(1000) / 144; +}; +} // namespace cru::ui::host diff --git a/include/cru/ui/host/WindowHost.hpp b/include/cru/ui/host/WindowHost.hpp new file mode 100644 index 00000000..bd2f7c16 --- /dev/null +++ b/include/cru/ui/host/WindowHost.hpp @@ -0,0 +1,176 @@ +#pragma once +#include "../Base.hpp" + +#include "../render/Base.hpp" +#include "cru/common/Event.hpp" +#include "cru/platform/gui/UiApplication.hpp" +#include "cru/platform/gui/Window.hpp" + +#include <functional> +#include <memory> +#include <optional> + +namespace cru::ui::host { +class LayoutPaintCycler; + +struct AfterLayoutEventArgs {}; + +struct CreateWindowParams { + CreateWindowParams(platform::gui::INativeWindow* parent = nullptr, + platform::gui::CreateWindowFlag flag = {}) + : parent(parent), flag(flag) {} + + platform::gui::INativeWindow* parent; + platform::gui::CreateWindowFlag flag; +}; + +// The bridge between control tree and native window. +class WindowHost : public Object { + CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::host::WindowHost") + + public: + WindowHost(controls::Control* root_control); + + CRU_DELETE_COPY(WindowHost) + CRU_DELETE_MOVE(WindowHost) + + ~WindowHost() override; + + public: + platform::gui::INativeWindow* GetNativeWindow() { return native_window_; } + + // Do nothing if native window is already created. + gsl::not_null<platform::gui::INativeWindow*> CreateNativeWindow( + CreateWindowParams create_window_params = {}); + + // Mark the layout as invalid, and arrange a re-layout later. + // This method could be called more than one times in a message cycle. But + // layout only takes place once. + void InvalidateLayout(); + + // Mark the paint as invalid, and arrange a re-paint later. + // This method could be called more than one times in a message cycle. But + // paint only takes place once. + void InvalidatePaint(); + + IEvent<AfterLayoutEventArgs>* AfterLayoutEvent() { + return &after_layout_event_; + } + + void Relayout(); + void Relayout(const Size& available_size); + + void Repaint(); + + // Is layout is invalid, wait for relayout and then run the action. Otherwist + // run it right now. + void RunAfterLayoutStable(std::function<void()> action); + + // If true, preferred size of root render object is set to window size when + // measure. Default is true. + bool IsLayoutPreferToFillWindow() const; + void SetLayoutPreferToFillWindow(bool value); + + // Get current control that mouse hovers on. This ignores the mouse-capture + // control. Even when mouse is captured by another control, this function + // return the control under cursor. You can use `GetMouseCaptureControl` to + // get more info. + controls::Control* GetMouseHoverControl() const { + return mouse_hover_control_; + } + + //*************** region: focus *************** + + controls::Control* GetFocusControl(); + + void SetFocusControl(controls::Control* control); + + //*************** region: focus *************** + + // Pass nullptr to release capture. If mouse is already capture by a control, + // this capture will fail and return false. If control is identical to the + // capturing control, capture is not changed and this function will return + // true. + // + // When capturing control changes, + // appropriate event will be sent. If mouse is not on the capturing control + // and capture is released, mouse enter event will be sent to the mouse-hover + // control. If mouse is not on the capturing control and capture is set, mouse + // leave event will be sent to the mouse-hover control. + bool CaptureMouseFor(controls::Control* control); + + // Return null if not captured. + controls::Control* GetMouseCaptureControl(); + + controls::Control* HitTest(const Point& point); + + void UpdateCursor(); + + IEvent<platform::gui::INativeWindow*>* NativeWindowChangeEvent() { + return &native_window_change_event_; + } + + // If window exist, return window actual size. Otherwise if saved rect exists, + // return it. Otherwise return 0. + Rect GetWindowRect(); + + void SetSavedWindowRect(std::optional<Rect> rect); + + void SetWindowRect(const Rect& rect); + + private: + //*************** region: native messages *************** + void OnNativeDestroy(platform::gui::INativeWindow* window, std::nullptr_t); + void OnNativePaint(platform::gui::INativeWindow* window, std::nullptr_t); + void OnNativeResize(platform::gui::INativeWindow* window, const Size& size); + + void OnNativeFocus(platform::gui::INativeWindow* window, + cru::platform::gui::FocusChangeType focus); + + void OnNativeMouseEnterLeave(platform::gui::INativeWindow* window, + cru::platform::gui::MouseEnterLeaveType enter); + void OnNativeMouseMove(platform::gui::INativeWindow* window, + const Point& point); + void OnNativeMouseDown(platform::gui::INativeWindow* window, + const platform::gui::NativeMouseButtonEventArgs& args); + void OnNativeMouseUp(platform::gui::INativeWindow* window, + const platform::gui::NativeMouseButtonEventArgs& args); + + void OnNativeKeyDown(platform::gui::INativeWindow* window, + const platform::gui::NativeKeyEventArgs& args); + void OnNativeKeyUp(platform::gui::INativeWindow* window, + const platform::gui::NativeKeyEventArgs& args); + + //*************** region: event dispatcher helper *************** + + void DispatchMouseHoverControlChangeEvent(controls::Control* old_control, + controls::Control* new_control, + const Point& point, bool no_leave, + bool no_enter); + + private: + controls::Control* root_control_ = nullptr; + render::RenderObject* root_render_object_ = nullptr; + + platform::gui::INativeWindow* native_window_ = nullptr; + + std::unique_ptr<LayoutPaintCycler> layout_paint_cycler_; + + Event<AfterLayoutEventArgs> after_layout_event_; + std::vector<std::function<void()> > after_layout_stable_action_; + + std::vector<EventRevokerGuard> event_revoker_guards_; + + controls::Control* mouse_hover_control_ = nullptr; + + controls::Control* focus_control_; + + controls::Control* mouse_captured_control_ = nullptr; + + bool layout_prefer_to_fill_window_ = true; + + Event<platform::gui::INativeWindow*> native_window_change_event_; + + std::optional<Rect> saved_rect_; +}; +} // namespace cru::ui::host diff --git a/include/cru/ui/render/Base.hpp b/include/cru/ui/render/Base.hpp index 801d58bd..ac67349e 100644 --- a/include/cru/ui/render/Base.hpp +++ b/include/cru/ui/render/Base.hpp @@ -9,5 +9,4 @@ class FlexLayoutRenderObject; class ScrollRenderObject; class StackLayoutRenderObject; class TextRenderObject; -class WindowRenderObject; } // namespace cru::ui::render diff --git a/include/cru/ui/render/BorderRenderObject.hpp b/include/cru/ui/render/BorderRenderObject.hpp index 587f051a..3d4f4dad 100644 --- a/include/cru/ui/render/BorderRenderObject.hpp +++ b/include/cru/ui/render/BorderRenderObject.hpp @@ -1,5 +1,7 @@ #pragma once +#include "../style/ApplyBorderStyleInfo.hpp" #include "RenderObject.hpp" +#include "cru/ui/Base.hpp" namespace cru::ui::render { class BorderRenderObject : public RenderObject { @@ -16,11 +18,11 @@ class BorderRenderObject : public RenderObject { bool IsBorderEnabled() const { return is_border_enabled_; } void SetBorderEnabled(bool enabled) { is_border_enabled_ = enabled; } - std::shared_ptr<platform::graph::IBrush> GetBorderBrush() { + std::shared_ptr<platform::graphics::IBrush> GetBorderBrush() { return border_brush_; } - void SetBorderBrush(std::shared_ptr<platform::graph::IBrush> brush) { + void SetBorderBrush(std::shared_ptr<platform::graphics::IBrush> brush) { if (brush == border_brush_) return; border_brush_ = std::move(brush); InvalidatePaint(); @@ -42,32 +44,32 @@ class BorderRenderObject : public RenderObject { RecreateGeometry(); } - std::shared_ptr<platform::graph::IBrush> GetForegroundBrush() { + std::shared_ptr<platform::graphics::IBrush> GetForegroundBrush() { return foreground_brush_; } - void SetForegroundBrush(std::shared_ptr<platform::graph::IBrush> brush) { + void SetForegroundBrush(std::shared_ptr<platform::graphics::IBrush> brush) { if (brush == foreground_brush_) return; foreground_brush_ = std::move(brush); InvalidatePaint(); } - std::shared_ptr<platform::graph::IBrush> GetBackgroundBrush() { + std::shared_ptr<platform::graphics::IBrush> GetBackgroundBrush() { return background_brush_; } - void SetBackgroundBrush(std::shared_ptr<platform::graph::IBrush> brush) { + void SetBackgroundBrush(std::shared_ptr<platform::graphics::IBrush> brush) { if (brush == background_brush_) return; background_brush_ = std::move(brush); InvalidatePaint(); } - void SetBorderStyle(const BorderStyle& style); + void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style); RenderObject* HitTest(const Point& point) override; protected: - void OnDrawCore(platform::graph::IPainter* painter) override; + void OnDrawCore(platform::graphics::IPainter* painter) override; Size OnMeasureCore(const MeasureRequirement& requirement, const MeasureSize& preferred_size) override; @@ -87,19 +89,19 @@ class BorderRenderObject : public RenderObject { private: bool is_border_enabled_ = false; - std::shared_ptr<platform::graph::IBrush> border_brush_; + std::shared_ptr<platform::graphics::IBrush> border_brush_; Thickness border_thickness_; CornerRadius border_radius_; - std::shared_ptr<platform::graph::IBrush> foreground_brush_; - std::shared_ptr<platform::graph::IBrush> background_brush_; + std::shared_ptr<platform::graphics::IBrush> foreground_brush_; + std::shared_ptr<platform::graphics::IBrush> background_brush_; // The ring. Used for painting. - std::unique_ptr<platform::graph::IGeometry> geometry_; + std::unique_ptr<platform::graphics::IGeometry> geometry_; // Area including inner area of the border. Used for painting foreground and // background. - std::unique_ptr<platform::graph::IGeometry> border_inner_geometry_; + std::unique_ptr<platform::graphics::IGeometry> border_inner_geometry_; // Area including border ring and inner area. Used for hit test. - std::unique_ptr<platform::graph::IGeometry> border_outer_geometry_; + std::unique_ptr<platform::graphics::IGeometry> border_outer_geometry_; }; } // namespace cru::ui::render diff --git a/include/cru/ui/render/CanvasRenderObject.hpp b/include/cru/ui/render/CanvasRenderObject.hpp index 3216f08c..58fee59c 100644 --- a/include/cru/ui/render/CanvasRenderObject.hpp +++ b/include/cru/ui/render/CanvasRenderObject.hpp @@ -22,7 +22,7 @@ class CanvasRenderObject : public RenderObject { IEvent<CanvasPaintEventArgs>* PaintEvent() { return &paint_event_; } protected: - void OnDrawContent(platform::graph::IPainter* painter) override; + void OnDrawContent(platform::graphics::IPainter* painter) override; Size OnMeasureContent(const MeasureRequirement& requirement, const MeasureSize& preferred_size) override; 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 <string_view> + 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<FlexChildLayoutData> { 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/LayoutRenderObject.hpp b/include/cru/ui/render/LayoutRenderObject.hpp index b46ba0d0..732031a1 100644 --- a/include/cru/ui/render/LayoutRenderObject.hpp +++ b/include/cru/ui/render/LayoutRenderObject.hpp @@ -1,7 +1,7 @@ #pragma once #include "RenderObject.hpp" -#include "cru/platform/graph/util/Painter.hpp" +#include "cru/platform/graphics/util/Painter.hpp" namespace cru::ui::render { template <typename TChildLayoutData> 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 <fmt/core.h> #include <algorithm> #include <limits> +#include <string> 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..8bcd4c62 100644 --- a/include/cru/ui/render/RenderObject.hpp +++ b/include/cru/ui/render/RenderObject.hpp @@ -2,10 +2,15 @@ #include "Base.hpp" #include "MeasureRequirement.hpp" +#include "cru/common/Base.hpp" #include "cru/common/Event.hpp" +#include "cru/ui/Base.hpp" -namespace cru::ui::render { +#include <cstddef> +#include <string> +#include <string_view> +namespace cru::ui::render { // Render object will not destroy its children when destroyed. Control must // manage lifecycle of its render objects. Since control will destroy its // children when destroyed, render objects will be destroyed along with it. @@ -29,13 +34,13 @@ namespace cru::ui::render { // // To write a custom RenderObject, override following methods: // public: -// void Draw(platform::graph::IPainter* painter) override; +// void Draw(platform::graphics::IPainter* painter) override; // RenderObject* HitTest(const Point& point) override; // protected: // Size OnMeasureContent(const MeasureRequirement& requirement) override; // void OnLayoutContent(const Rect& content_rect) override; class RenderObject : public Object { - friend WindowRenderObject; + friend host::WindowHost; CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::render::RenderObject") @@ -58,10 +63,10 @@ class RenderObject : public Object { RenderObject& operator=(RenderObject&& other) = delete; ~RenderObject() override = default; - Control* GetAttachedControl() const { return control_; } - void SetAttachedControl(Control* new_control) { control_ = new_control; } + controls::Control* GetAttachedControl() const { return control_; } + void SetAttachedControl(controls::Control* new_control); - UiHost* GetUiHost() const { return ui_host_; } + host::WindowHost* GetWindowHost() const { return window_host_; } RenderObject* GetParent() const { return parent_; } @@ -70,6 +75,9 @@ class RenderObject : public Object { void AddChild(RenderObject* render_object, Index position); void RemoveChild(Index position); + RenderObject* GetFirstChild() const; + void TraverseDescendants(const std::function<void(RenderObject*)>& action); + // Offset from parent's lefttop to lefttop of this render object. Margin is // accounted for. Point GetOffset() const { return offset_; } @@ -123,16 +131,30 @@ class RenderObject : public Object { // This will set offset of this render object and call OnLayoutCore. void Layout(const Point& offset); - void Draw(platform::graph::IPainter* painter); + virtual Rect GetPaddingRect() const; + virtual Rect GetContentRect() const; + + void Draw(platform::graphics::IPainter* painter); // Param point must be relative the lefttop of render object including margin. // Add offset before pass point to children. virtual RenderObject* HitTest(const Point& point) = 0; + IEvent<host::WindowHost*>* AttachToHostEvent() { + return &attach_to_host_event_; + } + IEvent<std::nullptr_t>* DetachFromHostEvent() { + return &detach_from_host_event_; + } + public: void InvalidateLayout(); void InvalidatePaint(); + public: + virtual std::u16string_view GetName() const; + std::u16string GetDebugPathInTree() const; + protected: void SetChildMode(ChildMode mode) { child_mode_ = mode; } @@ -148,15 +170,15 @@ class RenderObject : public Object { virtual void OnRemoveChild(RenderObject* removed_child, Index position); // Draw all children with offset. - void DefaultDrawChildren(platform::graph::IPainter* painter); + void DefaultDrawChildren(platform::graphics::IPainter* painter); // Draw all children with translation of content rect lefttop. - void DefaultDrawContent(platform::graph::IPainter* painter); + void DefaultDrawContent(platform::graphics::IPainter* painter); // Call DefaultDrawContent. Then call DefaultDrawChildren. - virtual void OnDrawCore(platform::graph::IPainter* painter); + virtual void OnDrawCore(platform::graphics::IPainter* painter); - virtual void OnDrawContent(platform::graph::IPainter* painter); + virtual void OnDrawContent(platform::graphics::IPainter* painter); // Size measure including margin and padding. Please reduce margin and padding // or other custom things and pass the result content measure requirement and @@ -182,20 +204,20 @@ class RenderObject : public Object { // Lefttop of content_rect should be added when calculated children's offset. virtual void OnLayoutContent(const Rect& content_rect) = 0; - virtual void OnAfterLayout(); - static void NotifyAfterLayoutRecursive(RenderObject* render_object); + virtual void OnAttachedControlChanged(controls::Control* control) { + CRU_UNUSED(control) + } - virtual Rect GetPaddingRect() const; - virtual Rect GetContentRect() const; + virtual void OnAfterLayout(); private: void SetParent(RenderObject* new_parent); - void SetRenderHostRecursive(UiHost* host); + void SetWindowHostRecursive(host::WindowHost* host); private: - Control* control_ = nullptr; - UiHost* ui_host_ = nullptr; + controls::Control* control_ = nullptr; + host::WindowHost* window_host_ = nullptr; RenderObject* parent_ = nullptr; std::vector<RenderObject*> children_{}; @@ -210,5 +232,8 @@ class RenderObject : public Object { Thickness margin_{}; Thickness padding_{}; + + Event<host::WindowHost*> attach_to_host_event_; + Event<std::nullptr_t> detach_from_host_event_; }; } // namespace cru::ui::render diff --git a/include/cru/ui/render/ScrollBar.hpp b/include/cru/ui/render/ScrollBar.hpp new file mode 100644 index 00000000..3293e9d0 --- /dev/null +++ b/include/cru/ui/render/ScrollBar.hpp @@ -0,0 +1,218 @@ +#pragma once +#include "Base.hpp" +#include "cru/common/Base.hpp" +#include "cru/common/Event.hpp" +#include "cru/platform/graphics/Base.hpp" +#include "cru/platform/graphics/Geometry.hpp" +#include "cru/platform/graphics/Painter.hpp" +#include "cru/platform/gui/Cursor.hpp" +#include "cru/platform/gui/UiApplication.hpp" +#include "cru/ui/Base.hpp" +#include "cru/ui/controls/Control.hpp" + +#include <gsl/pointers> +#include <memory> +#include <optional> + +namespace cru::ui::render { +class ScrollRenderObject; + +enum class ScrollKind { Absolute, Relative, Page, Line }; + +struct Scroll { + Direction direction; + ScrollKind kind; + // For absolute, the new scroll position. Otherwise, offset. + float value; +}; + +enum class ScrollBarAreaKind { + UpArrow, // Line up + DownArrow, // Line down + UpSlot, // Page up + DownSlot, // Page down + Thumb +}; + +class ScrollBar : public Object { + public: + ScrollBar(gsl::not_null<ScrollRenderObject*> render_object, + Direction direction); + + CRU_DELETE_COPY(ScrollBar) + CRU_DELETE_MOVE(ScrollBar) + + ~ScrollBar() override; + + public: + Direction GetDirection() const { return direction_; } + + bool IsEnabled() const { return is_enabled_; } + void SetEnabled(bool value); + + bool IsExpanded() const { return is_expanded_; } + void SetExpanded(bool value); + + void Draw(platform::graphics::IPainter* painter); + + IEvent<Scroll>* ScrollAttemptEvent() { return &scroll_attempt_event_; } + + void InstallHandlers(controls::Control* control); + void UninstallHandlers() { InstallHandlers(nullptr); } + + gsl::not_null<std::shared_ptr<platform::graphics::IBrush>> + GetCollapsedThumbBrush() const; + gsl::not_null<std::shared_ptr<platform::graphics::IBrush>> + GetExpandedThumbBrush() const; + gsl::not_null<std::shared_ptr<platform::graphics::IBrush>> + GetExpandedSlotBrush() const; + gsl::not_null<std::shared_ptr<platform::graphics::IBrush>> + GetExpandedArrowBrush() const; + gsl::not_null<std::shared_ptr<platform::graphics::IBrush>> + GetExpandedArrowBackgroundBrush() const; + + protected: + void OnDraw(platform::graphics::IPainter* painter, bool expand); + + virtual void DrawUpArrow(platform::graphics::IPainter* painter, + const Rect& area) = 0; + virtual void DrawDownArrow(platform::graphics::IPainter* painter, + const Rect& area) = 0; + + std::optional<ScrollBarAreaKind> ExpandedHitTest(const Point& point); + + virtual bool IsShowBar() = 0; + + virtual std::optional<Rect> GetExpandedAreaRect( + ScrollBarAreaKind area_kind) = 0; + virtual std::optional<Rect> GetCollapsedTriggerExpandAreaRect() = 0; + virtual std::optional<Rect> GetCollapsedThumbRect() = 0; + + virtual float CalculateNewScrollPosition(const Rect& thumb_original_rect, + const Point& mouse_offset) = 0; + + private: + void SetCursor(); + void RestoreCursor(); + + void BeginAutoCollapseTimer(); + void StopAutoCollapseTimer(); + + void OnMouseLeave(); + + protected: + gsl::not_null<ScrollRenderObject*> render_object_; + + std::unique_ptr<platform::graphics::IGeometry> arrow_geometry_; + + private: + Direction direction_; + + bool is_enabled_ = true; + + bool is_expanded_ = false; + + std::shared_ptr<platform::graphics::IBrush> collapsed_thumb_brush_; + std::shared_ptr<platform::graphics::IBrush> expanded_thumb_brush_; + std::shared_ptr<platform::graphics::IBrush> expanded_slot_brush_; + std::shared_ptr<platform::graphics::IBrush> expanded_arrow_brush_; + std::shared_ptr<platform::graphics::IBrush> expanded_arrow_background_brush_; + + Rect move_thumb_thumb_original_rect_; + std::optional<Point> move_thumb_start_; + + EventRevokerListGuard event_guard_; + + Event<Scroll> scroll_attempt_event_; + + std::optional<std::shared_ptr<platform::gui::ICursor>> old_cursor_; + + platform::gui::TimerAutoCanceler auto_collapse_timer_canceler_; +}; + +class HorizontalScrollBar : public ScrollBar { + public: + explicit HorizontalScrollBar( + gsl::not_null<ScrollRenderObject*> render_object); + + CRU_DELETE_COPY(HorizontalScrollBar) + CRU_DELETE_MOVE(HorizontalScrollBar) + + ~HorizontalScrollBar() override = default; + + protected: + void DrawUpArrow(platform::graphics::IPainter* painter, + const Rect& area) override; + void DrawDownArrow(platform::graphics::IPainter* painter, + const Rect& area) override; + + bool IsShowBar() override; + + std::optional<Rect> GetExpandedAreaRect(ScrollBarAreaKind area_kind) override; + std::optional<Rect> GetCollapsedTriggerExpandAreaRect() override; + std::optional<Rect> GetCollapsedThumbRect() override; + + float CalculateNewScrollPosition(const Rect& thumb_original_rect, + const Point& mouse_offset) override; +}; + +class VerticalScrollBar : public ScrollBar { + public: + explicit VerticalScrollBar(gsl::not_null<ScrollRenderObject*> render_object); + + CRU_DELETE_COPY(VerticalScrollBar) + CRU_DELETE_MOVE(VerticalScrollBar) + + ~VerticalScrollBar() override = default; + + protected: + void DrawUpArrow(platform::graphics::IPainter* painter, + const Rect& area) override; + void DrawDownArrow(platform::graphics::IPainter* painter, + const Rect& area) override; + + bool IsShowBar() override; + + std::optional<Rect> GetExpandedAreaRect(ScrollBarAreaKind area_kind) override; + std::optional<Rect> GetCollapsedTriggerExpandAreaRect() override; + std::optional<Rect> GetCollapsedThumbRect() override; + + float CalculateNewScrollPosition(const Rect& thumb_original_rect, + const Point& mouse_offset) override; +}; + +// A delegate to draw scrollbar and register related events. +class ScrollBarDelegate : public Object { + public: + explicit ScrollBarDelegate(gsl::not_null<ScrollRenderObject*> render_object); + + CRU_DELETE_COPY(ScrollBarDelegate) + CRU_DELETE_MOVE(ScrollBarDelegate) + + ~ScrollBarDelegate() override = default; + + public: + bool IsHorizontalBarEnabled() const { return horizontal_bar_.IsEnabled(); } + void SetHorizontalBarEnabled(bool value) { + horizontal_bar_.SetEnabled(value); + } + + bool IsVerticalBarEnabled() const { return horizontal_bar_.IsEnabled(); } + void SetVerticalBarEnabled(bool value) { horizontal_bar_.SetEnabled(value); } + + IEvent<Scroll>* ScrollAttemptEvent() { return &scroll_attempt_event_; } + + void DrawScrollBar(platform::graphics::IPainter* painter); + + void InstallHandlers(controls::Control* control); + void UninstallHandlers() { InstallHandlers(nullptr); } + + private: + gsl::not_null<ScrollRenderObject*> render_object_; + + HorizontalScrollBar horizontal_bar_; + VerticalScrollBar vertical_bar_; + + Event<Scroll> scroll_attempt_event_; +}; +} // namespace cru::ui::render diff --git a/include/cru/ui/render/ScrollRenderObject.hpp b/include/cru/ui/render/ScrollRenderObject.hpp index 9b0cbf9a..aed25f8e 100644 --- a/include/cru/ui/render/ScrollRenderObject.hpp +++ b/include/cru/ui/render/ScrollRenderObject.hpp @@ -1,8 +1,11 @@ #pragma once #include "RenderObject.hpp" -#include "cru/platform/graph/util/Painter.hpp" +#include "cru/platform/graphics/util/Painter.hpp" +#include "cru/ui/Base.hpp" +#include "cru/ui/render/ScrollBar.hpp" +#include <memory> #include <optional> namespace cru::ui::render { @@ -16,7 +19,7 @@ namespace cru::ui::render { // Or layout by scroll state. class ScrollRenderObject : public RenderObject { public: - ScrollRenderObject() : RenderObject(ChildMode::Single) {} + ScrollRenderObject(); CRU_DELETE_COPY(ScrollRenderObject) CRU_DELETE_MOVE(ScrollRenderObject) @@ -27,8 +30,22 @@ class ScrollRenderObject : public RenderObject { // Return the coerced scroll offset. Point GetScrollOffset(); + float GetScrollOffset(Direction direction) { + return direction == Direction::Horizontal ? GetScrollOffset().x + : GetScrollOffset().y; + } void SetScrollOffset(const Point& offset); void SetScrollOffset(std::optional<float> x, std::optional<float> y); + void SetScrollOffset(Direction direction, std::optional<float> value) { + if (direction == Direction::Horizontal) { + SetScrollOffset(value, std::nullopt); + } else { + SetScrollOffset(std::nullopt, value); + } + } + + void Scroll(const Scroll& scroll); + Point GetRawScrollOffset() const { return scroll_offset_; } // Return the viewable area rect. @@ -44,7 +61,7 @@ class ScrollRenderObject : public RenderObject { void ScrollToContain(const Rect& rect, const Thickness& margin = Thickness{}); protected: - void OnDrawCore(platform::graph::IPainter* painter) override; + void OnDrawCore(platform::graphics::IPainter* painter) override; // Logic: // If available size is bigger than child's preferred size, then child's @@ -54,7 +71,11 @@ class ScrollRenderObject : public RenderObject { const MeasureSize& preferred_size) override; void OnLayoutContent(const Rect& content_rect) override; + void OnAttachedControlChanged(controls::Control* control) override; + private: Point scroll_offset_; + + std::unique_ptr<ScrollBarDelegate> scroll_bar_delegate_; }; } // namespace cru::ui::render diff --git a/include/cru/ui/render/TextRenderObject.hpp b/include/cru/ui/render/TextRenderObject.hpp index 3be42bbb..bdec18d1 100644 --- a/include/cru/ui/render/TextRenderObject.hpp +++ b/include/cru/ui/render/TextRenderObject.hpp @@ -24,10 +24,10 @@ class TextRenderObject : public RenderObject { constexpr static float default_caret_width = 2; public: - TextRenderObject(std::shared_ptr<platform::graph::IBrush> brush, - std::shared_ptr<platform::graph::IFont> font, - std::shared_ptr<platform::graph::IBrush> selection_brush, - std::shared_ptr<platform::graph::IBrush> caret_brush); + TextRenderObject(std::shared_ptr<platform::graphics::IBrush> brush, + std::shared_ptr<platform::graphics::IFont> font, + std::shared_ptr<platform::graphics::IBrush> selection_brush, + std::shared_ptr<platform::graphics::IBrush> caret_brush); TextRenderObject(const TextRenderObject& other) = delete; TextRenderObject(TextRenderObject&& other) = delete; TextRenderObject& operator=(const TextRenderObject& other) = delete; @@ -38,25 +38,27 @@ class TextRenderObject : public RenderObject { std::u16string_view GetTextView() const; void SetText(std::u16string new_text); - std::shared_ptr<platform::graph::IBrush> GetBrush() const { return brush_; } - void SetBrush(std::shared_ptr<platform::graph::IBrush> new_brush); + std::shared_ptr<platform::graphics::IBrush> GetBrush() const { + return brush_; + } + void SetBrush(std::shared_ptr<platform::graphics::IBrush> new_brush); - std::shared_ptr<platform::graph::IFont> GetFont() const; - void SetFont(std::shared_ptr<platform::graph::IFont> font); + std::shared_ptr<platform::graphics::IFont> GetFont() const; + void SetFont(std::shared_ptr<platform::graphics::IFont> font); std::vector<Rect> TextRangeRect(const TextRange& text_range); Point TextSinglePoint(gsl::index position, bool trailing); - platform::graph::TextHitTestResult TextHitTest(const Point& point); + platform::graphics::TextHitTestResult TextHitTest(const Point& point); std::optional<TextRange> GetSelectionRange() const { return selection_range_; } void SetSelectionRange(std::optional<TextRange> new_range); - std::shared_ptr<platform::graph::IBrush> GetSelectionBrush() const { + std::shared_ptr<platform::graphics::IBrush> GetSelectionBrush() const { return selection_brush_; } - void SetSelectionBrush(std::shared_ptr<platform::graph::IBrush> new_brush); + void SetSelectionBrush(std::shared_ptr<platform::graphics::IBrush> new_brush); bool IsDrawCaret() const { return draw_caret_; } void SetDrawCaret(bool draw_caret); @@ -72,18 +74,23 @@ class TextRenderObject : public RenderObject { // Lefttop relative to render object lefttop. Rect GetCaretRect(); - std::shared_ptr<platform::graph::IBrush> GetCaretBrush() const { + std::shared_ptr<platform::graphics::IBrush> GetCaretBrush() const { return caret_brush_; } - void GetCaretBrush(std::shared_ptr<platform::graph::IBrush> brush); + void GetCaretBrush(std::shared_ptr<platform::graphics::IBrush> brush); float GetCaretWidth() const { return caret_width_; } void SetCaretWidth(float width); + bool IsMeasureIncludingTrailingSpace() const { + return is_measure_including_trailing_space_; + } + void SetMeasureIncludingTrailingSpace(bool including); + RenderObject* HitTest(const Point& point) override; protected: - void OnDrawContent(platform::graph::IPainter* painter) override; + void OnDrawContent(platform::graphics::IPainter* painter) override; // See remarks of this class. Size OnMeasureContent(const MeasureRequirement& requirement, @@ -93,16 +100,18 @@ class TextRenderObject : public RenderObject { void OnAfterLayout() override; private: - std::shared_ptr<platform::graph::IBrush> brush_; - std::shared_ptr<platform::graph::IFont> font_; - std::unique_ptr<platform::graph::ITextLayout> text_layout_; + std::shared_ptr<platform::graphics::IBrush> brush_; + std::shared_ptr<platform::graphics::IFont> font_; + std::unique_ptr<platform::graphics::ITextLayout> text_layout_; std::optional<TextRange> selection_range_ = std::nullopt; - std::shared_ptr<platform::graph::IBrush> selection_brush_; + std::shared_ptr<platform::graphics::IBrush> selection_brush_; bool draw_caret_ = false; gsl::index caret_position_ = 0; - std::shared_ptr<platform::graph::IBrush> caret_brush_; + std::shared_ptr<platform::graphics::IBrush> caret_brush_; float caret_width_ = default_caret_width; + + bool is_measure_including_trailing_space_ = false; }; } // namespace cru::ui::render diff --git a/include/cru/ui/render/WindowRenderObject.hpp b/include/cru/ui/render/WindowRenderObject.hpp deleted file mode 100644 index 4c254f42..00000000 --- a/include/cru/ui/render/WindowRenderObject.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include "RenderObject.hpp" - -namespace cru::ui::render { -class WindowRenderObject : public RenderObject { - public: - WindowRenderObject(UiHost* host); - WindowRenderObject(const WindowRenderObject& other) = delete; - WindowRenderObject(WindowRenderObject&& other) = delete; - WindowRenderObject& operator=(const WindowRenderObject& other) = delete; - WindowRenderObject& operator=(WindowRenderObject&& other) = delete; - ~WindowRenderObject() override = default; - - RenderObject* HitTest(const Point& point) override; - - protected: - Size OnMeasureContent(const MeasureRequirement& requirement, - const MeasureSize& preferred_size) override; - void OnLayoutContent(const Rect& content_rect) override; - - private: - RenderObject* GetChild() const { - return GetChildren().empty() ? nullptr : GetChildren()[0]; - } - - private: - EventRevokerGuard after_layout_event_guard_; -}; -} // namespace cru::ui::render diff --git a/include/cru/ui/style/ApplyBorderStyleInfo.hpp b/include/cru/ui/style/ApplyBorderStyleInfo.hpp new file mode 100644 index 00000000..5058b51f --- /dev/null +++ b/include/cru/ui/style/ApplyBorderStyleInfo.hpp @@ -0,0 +1,28 @@ +#pragma once +#include <optional> +#include "../Base.hpp" + +namespace cru::ui::style { +struct ApplyBorderStyleInfo { + explicit ApplyBorderStyleInfo( + std::optional<std::shared_ptr<platform::graphics::IBrush>> border_brush = + std::nullopt, + std::optional<Thickness> border_thickness = std::nullopt, + std::optional<CornerRadius> border_radius = std::nullopt, + std::optional<std::shared_ptr<platform::graphics::IBrush>> + foreground_brush = std::nullopt, + std::optional<std::shared_ptr<platform::graphics::IBrush>> + background_brush = std::nullopt) + : border_brush(std::move(border_brush)), + border_thickness(std::move(border_thickness)), + border_radius(std::move(border_radius)), + foreground_brush(std::move(foreground_brush)), + background_brush(std::move(background_brush)) {} + + std::optional<std::shared_ptr<platform::graphics::IBrush>> border_brush; + std::optional<Thickness> border_thickness; + std::optional<CornerRadius> border_radius; + std::optional<std::shared_ptr<platform::graphics::IBrush>> foreground_brush; + std::optional<std::shared_ptr<platform::graphics::IBrush>> background_brush; +}; +} // namespace cru::ui::style diff --git a/include/cru/ui/style/Condition.hpp b/include/cru/ui/style/Condition.hpp new file mode 100644 index 00000000..d5cf16f2 --- /dev/null +++ b/include/cru/ui/style/Condition.hpp @@ -0,0 +1,123 @@ +#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" + +#include <memory> +#include <type_traits> +#include <utility> +#include <vector> + +namespace cru::ui::style { +class Condition : public Object { + public: + virtual std::vector<IBaseEvent*> ChangeOn( + controls::Control* control) const = 0; + virtual bool Judge(controls::Control* control) const = 0; + + virtual Condition* Clone() const = 0; +}; + +class NoCondition : public Condition { + public: + static ClonablePtr<NoCondition> Create() { + return ClonablePtr<NoCondition>(new NoCondition); + }; + + std::vector<IBaseEvent*> 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<ClonablePtr<Condition>> conditions); + + std::vector<IBaseEvent*> ChangeOn(controls::Control* control) const override; + + protected: + std::vector<ClonablePtr<Condition>> conditions_; +}; + +class AndCondition : public CompoundCondition { + public: + using CompoundCondition::CompoundCondition; + + bool Judge(controls::Control* control) const override; + + AndCondition* Clone() const override { return new AndCondition(conditions_); } +}; + +class OrCondition : public CompoundCondition { + public: + using CompoundCondition::CompoundCondition; + + bool Judge(controls::Control* control) const override; + + OrCondition* Clone() const override { return new OrCondition(conditions_); } +}; + +class FocusCondition : public Condition { + public: + static ClonablePtr<FocusCondition> Create(bool has_focus) { + return ClonablePtr<FocusCondition>(new FocusCondition(has_focus)); + } + + explicit FocusCondition(bool has_focus); + + std::vector<IBaseEvent*> ChangeOn(controls::Control* control) const override; + bool Judge(controls::Control* control) const override; + + FocusCondition* Clone() const override { + return new FocusCondition(has_focus_); + } + + private: + bool has_focus_; +}; + +class HoverCondition : public Condition { + public: + static ClonablePtr<HoverCondition> Create(bool hover) { + return ClonablePtr<HoverCondition>(new HoverCondition(hover)); + } + + explicit HoverCondition(bool hover) : hover_(hover) {} + + std::vector<IBaseEvent*> 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<ClickStateCondition> Create( + helper::ClickState click_state) { + return ClonablePtr<ClickStateCondition>( + new ClickStateCondition(click_state)); + } + + explicit ClickStateCondition(helper::ClickState click_state); + + std::vector<IBaseEvent*> ChangeOn(controls::Control* control) const override; + bool Judge(controls::Control* control) const override; + + ClickStateCondition* Clone() const override { + return new ClickStateCondition(click_state_); + } + + private: + helper::ClickState click_state_; +}; +} // namespace cru::ui::style diff --git a/include/cru/ui/style/StyleRule.hpp b/include/cru/ui/style/StyleRule.hpp new file mode 100644 index 00000000..8ac42cd0 --- /dev/null +++ b/include/cru/ui/style/StyleRule.hpp @@ -0,0 +1,45 @@ +#pragma once +#include "Condition.hpp" +#include "Styler.hpp" +#include "cru/common/Base.hpp" +#include "cru/common/ClonablePtr.hpp" +#include "cru/ui/Base.hpp" + +#include <memory> +#include <string> +#include <vector> + +namespace cru::ui::style { +class StyleRule : public Object { + public: + StyleRule(ClonablePtr<Condition> condition, ClonablePtr<Styler> styler, + std::u16string name = {}); + + CRU_DEFAULT_COPY(StyleRule) + CRU_DEFAULT_MOVE(StyleRule) + + ~StyleRule() override = default; + + public: + const std::u16string& GetName() const { return name_; } + Condition* GetCondition() const { return condition_.get(); } + Styler* GetStyler() const { return styler_.get(); } + + StyleRule WithNewCondition(ClonablePtr<Condition> condition, + std::u16string name = {}) const { + return StyleRule{std::move(condition), styler_, std::move(name)}; + } + + StyleRule WithNewStyler(ClonablePtr<Styler> styler, + std::u16string name = {}) const { + return StyleRule{condition_, std::move(styler), std::move(name)}; + } + + bool CheckAndApply(controls::Control* control) const; + + private: + ClonablePtr<Condition> condition_; + ClonablePtr<Styler> 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..e62dd2de --- /dev/null +++ b/include/cru/ui/style/StyleRuleSet.hpp @@ -0,0 +1,87 @@ +#pragma once +#include "StyleRule.hpp" +#include "cru/common/Base.hpp" +#include "cru/common/Event.hpp" + +#include <cstddef> + +namespace cru::ui::style { +class StyleRuleSet : public Object { + public: + StyleRuleSet() = default; + explicit StyleRuleSet(StyleRuleSet* parent); + + CRU_DELETE_COPY(StyleRuleSet) + CRU_DELETE_MOVE(StyleRuleSet) + + ~StyleRuleSet() override = default; + + public: + StyleRuleSet* GetParent() const { return parent_; } + void SetParent(StyleRuleSet* parent); + + gsl::index GetSize() const { return static_cast<gsl::index>(rules_.size()); } + const std::vector<StyleRule>& GetRules() const { return rules_; } + + void AddStyleRule(StyleRule rule) { + AddStyleRule(std::move(rule), GetSize()); + } + + void AddStyleRule(StyleRule rule, gsl::index index); + + template <typename Iter> + 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, bool set_parent = false); + + 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<std::nullptr_t>* ChangeEvent() { return &change_event_; } + + private: + void RaiseChangeEvent() { change_event_.Raise(nullptr); } + + private: + Event<std::nullptr_t> change_event_; + + StyleRuleSet* parent_ = nullptr; + EventRevokerGuard parent_change_event_guard_; + + std::vector<StyleRule> 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_; + + // child first, parent last. + std::vector<StyleRuleSet*> ruleset_chain_cache_; + + EventRevokerListGuard guard_; +}; +} // namespace cru::ui::style diff --git a/include/cru/ui/style/Styler.hpp b/include/cru/ui/style/Styler.hpp new file mode 100644 index 00000000..865cbbaf --- /dev/null +++ b/include/cru/ui/style/Styler.hpp @@ -0,0 +1,80 @@ +#pragma once +#include "../Base.hpp" +#include "ApplyBorderStyleInfo.hpp" +#include "cru/common/Base.hpp" +#include "cru/common/ClonablePtr.hpp" +#include "cru/platform/gui/Cursor.hpp" +#include "cru/ui/controls/Control.hpp" + +#include <memory> +#include <vector> + +namespace cru::ui::style { +class Styler : public Object { + public: + virtual void Apply(controls::Control* control) const = 0; + + virtual Styler* Clone() const = 0; +}; + +class CompoundStyler : public Styler { + public: + template <typename... S> + static ClonablePtr<CompoundStyler> Create(ClonablePtr<S>... s) { + return ClonablePtr<CompoundStyler>( + new CompoundStyler(std::vector<ClonablePtr<Styler>>{std::move(s)...})); + } + + explicit CompoundStyler(std::vector<ClonablePtr<Styler>> stylers) + : stylers_(std::move(stylers)) {} + + void Apply(controls::Control* control) const override { + for (const auto& styler : stylers_) { + styler->Apply(control); + } + } + + virtual CompoundStyler* Clone() const override { + return new CompoundStyler(stylers_); + } + + private: + std::vector<ClonablePtr<Styler>> stylers_; +}; + +class BorderStyler : public Styler { + public: + static ClonablePtr<BorderStyler> Create(ApplyBorderStyleInfo style) { + return ClonablePtr<BorderStyler>(new BorderStyler(std::move(style))); + } + + explicit BorderStyler(ApplyBorderStyleInfo style); + + void Apply(controls::Control* control) const override; + + BorderStyler* Clone() const override { return new BorderStyler(style_); } + + private: + ApplyBorderStyleInfo style_; +}; + +class CursorStyler : public Styler { + public: + static ClonablePtr<CursorStyler> Create( + std::shared_ptr<platform::gui::ICursor> cursor) { + return ClonablePtr<CursorStyler>(new CursorStyler(std::move(cursor))); + } + + static ClonablePtr<CursorStyler> Create(platform::gui::SystemCursorType type); + + explicit CursorStyler(std::shared_ptr<platform::gui::ICursor> cursor) + : cursor_(std::move(cursor)) {} + + void Apply(controls::Control* control) const override; + + CursorStyler* Clone() const override { return new CursorStyler(cursor_); } + + private: + std::shared_ptr<platform::gui::ICursor> cursor_; +}; +} // namespace cru::ui::style diff --git a/include/cru/win/graph/direct/Brush.hpp b/include/cru/win/graphics/direct/Brush.hpp index df1debe3..fbff83b5 100644 --- a/include/cru/win/graph/direct/Brush.hpp +++ b/include/cru/win/graphics/direct/Brush.hpp @@ -2,9 +2,9 @@ #include "ComResource.hpp" #include "Resource.hpp" -#include "cru/platform/graph/Brush.hpp" +#include "cru/platform/graphics/Brush.hpp" -namespace cru::platform::graph::win::direct { +namespace cru::platform::graphics::win::direct { struct ID2DBrush : virtual IBrush { virtual ID2D1Brush* GetD2DBrushInterface() const = 0; }; @@ -36,4 +36,4 @@ class D2DSolidColorBrush : public DirectGraphResource, Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> brush_; }; -} // namespace cru::platform::graph::win::direct +} // namespace cru::platform::graphics::win::direct diff --git a/include/cru/win/graph/direct/ComResource.hpp b/include/cru/win/graphics/direct/ComResource.hpp index 2ac332cd..34ea39ed 100644 --- a/include/cru/win/graph/direct/ComResource.hpp +++ b/include/cru/win/graphics/direct/ComResource.hpp @@ -3,9 +3,9 @@ #include "cru/common/Base.hpp" -namespace cru::platform::graph::win::direct { +namespace cru::platform::graphics::win::direct { template <typename TInterface> struct IComResource : virtual Interface { virtual TInterface* GetComInterface() const = 0; }; -} // namespace cru::platform::graph::win::direct +} // namespace cru::platform::graphics::win::direct diff --git a/include/cru/win/graph/direct/ConvertUtil.hpp b/include/cru/win/graphics/direct/ConvertUtil.hpp index 12a04c7b..0d8da8a1 100644 --- a/include/cru/win/graph/direct/ConvertUtil.hpp +++ b/include/cru/win/graphics/direct/ConvertUtil.hpp @@ -1,9 +1,9 @@ #pragma once #include "../../WinPreConfig.hpp" -#include "cru/platform/graph/Base.hpp" +#include "cru/platform/graphics/Base.hpp" -namespace cru::platform::graph::win::direct { +namespace cru::platform::graphics::win::direct { inline D2D1_MATRIX_3X2_F Convert(const platform::Matrix& matrix) { D2D1_MATRIX_3X2_F m; m._11 = matrix.m11; @@ -104,4 +104,4 @@ inline bool operator==(const D2D1_ELLIPSE& left, const D2D1_ELLIPSE& right) { inline bool operator!=(const D2D1_ELLIPSE& left, const D2D1_ELLIPSE& right) { return !(left == right); } -} // namespace cru::platform::graph::win::direct +} // namespace cru::platform::graphics::win::direct diff --git a/include/cru/win/graph/direct/Exception.hpp b/include/cru/win/graphics/direct/Exception.hpp index 8b62e8fa..72493f2f 100644 --- a/include/cru/win/graph/direct/Exception.hpp +++ b/include/cru/win/graphics/direct/Exception.hpp @@ -1,7 +1,7 @@ #pragma once #include "../../Exception.hpp" -namespace cru::platform::graph::win::direct { +namespace cru::platform::graphics::win::direct { using platform::win::HResultError; using platform::win::ThrowIfFailed; -} // namespace cru::platform::graph::win::direct
\ No newline at end of file +} // namespace cru::platform::graphics::win::direct diff --git a/include/cru/win/graph/direct/Factory.hpp b/include/cru/win/graphics/direct/Factory.hpp index e70454f5..70f3ede1 100644 --- a/include/cru/win/graph/direct/Factory.hpp +++ b/include/cru/win/graphics/direct/Factory.hpp @@ -1,9 +1,9 @@ #pragma once #include "Resource.hpp" -#include "cru/platform/graph/Factory.hpp" +#include "cru/platform/graphics/Factory.hpp" -namespace cru::platform::graph::win::direct { +namespace cru::platform::graphics::win::direct { class DirectGraphFactory : public DirectResource, public virtual IGraphFactory { public: DirectGraphFactory(); @@ -55,4 +55,4 @@ class DirectGraphFactory : public DirectResource, public virtual IGraphFactory { Microsoft::WRL::ComPtr<IDWriteFactory> dwrite_factory_; Microsoft::WRL::ComPtr<IDWriteFontCollection> dwrite_system_font_collection_; }; -} // namespace cru::platform::graph::win::direct +} // namespace cru::platform::graphics::win::direct diff --git a/include/cru/win/graph/direct/Font.hpp b/include/cru/win/graphics/direct/Font.hpp index 2195f3e4..fd3921a3 100644 --- a/include/cru/win/graph/direct/Font.hpp +++ b/include/cru/win/graphics/direct/Font.hpp @@ -2,11 +2,11 @@ #include "ComResource.hpp" #include "Resource.hpp" -#include "cru/platform/graph/Font.hpp" +#include "cru/platform/graphics/Font.hpp" #include <string_view> -namespace cru::platform::graph::win::direct { +namespace cru::platform::graphics::win::direct { class DWriteFont : public DirectGraphResource, public virtual IFont, public virtual IComResource<IDWriteTextFormat> { @@ -30,4 +30,4 @@ class DWriteFont : public DirectGraphResource, std::u16string font_family_; Microsoft::WRL::ComPtr<IDWriteTextFormat> text_format_; }; -} // namespace cru::platform::graph::win::direct +} // namespace cru::platform::graphics::win::direct diff --git a/include/cru/win/graph/direct/Geometry.hpp b/include/cru/win/graphics/direct/Geometry.hpp index 87987d3e..edfec590 100644 --- a/include/cru/win/graph/direct/Geometry.hpp +++ b/include/cru/win/graphics/direct/Geometry.hpp @@ -2,9 +2,9 @@ #include "ComResource.hpp" #include "Resource.hpp" -#include "cru/platform/graph/Geometry.hpp" +#include "cru/platform/graphics/Geometry.hpp" -namespace cru::platform::graph::win::direct { +namespace cru::platform::graphics::win::direct { class D2DGeometryBuilder : public DirectGraphResource, public virtual IGeometryBuilder { public: @@ -54,4 +54,4 @@ class D2DGeometry : public DirectGraphResource, private: Microsoft::WRL::ComPtr<ID2D1PathGeometry> geometry_; }; -} // namespace cru::platform::graph::win::direct +} // namespace cru::platform::graphics::win::direct diff --git a/include/cru/win/graph/direct/Painter.hpp b/include/cru/win/graphics/direct/Painter.hpp index a50f962d..b34c1563 100644 --- a/include/cru/win/graph/direct/Painter.hpp +++ b/include/cru/win/graphics/direct/Painter.hpp @@ -2,11 +2,11 @@ #include "ComResource.hpp" #include "Resource.hpp" -#include "cru/platform/graph/Painter.hpp" +#include "cru/platform/graphics/Painter.hpp" #include <vector> -namespace cru::platform::graph::win::direct { +namespace cru::platform::graphics::win::direct { class D2DPainter : public DirectResource, public virtual IPainter, public virtual IComResource<ID2D1RenderTarget> { @@ -27,6 +27,8 @@ class D2DPainter : public DirectResource, void Clear(const Color& color) override; + void DrawLine(const Point& start, const Point& end, IBrush* brush, + float width) override; void StrokeRectangle(const Rect& rectangle, IBrush* brush, float width) override; void FillRectangle(const Rect& rectangle, IBrush* brush) override; @@ -57,4 +59,4 @@ class D2DPainter : public DirectResource, bool is_drawing_ = true; }; -} // namespace cru::platform::graph::win::direct +} // namespace cru::platform::graphics::win::direct diff --git a/include/cru/win/graph/direct/Resource.hpp b/include/cru/win/graphics/direct/Resource.hpp index 6162ebd8..f60f373e 100644 --- a/include/cru/win/graph/direct/Resource.hpp +++ b/include/cru/win/graphics/direct/Resource.hpp @@ -1,11 +1,11 @@ #pragma once #include "../../WinPreConfig.hpp" -#include "cru/platform/graph/Resource.hpp" +#include "cru/platform/graphics/Resource.hpp" #include <string_view> -namespace cru::platform::graph::win::direct { +namespace cru::platform::graphics::win::direct { class DirectGraphFactory; class DirectResource : public Object, public virtual INativeResource { @@ -46,4 +46,4 @@ class DirectGraphResource : public DirectResource, private: DirectGraphFactory* factory_; }; -} // namespace cru::platform::graph::win::direct +} // namespace cru::platform::graphics::win::direct diff --git a/include/cru/win/graph/direct/TextLayout.hpp b/include/cru/win/graphics/direct/TextLayout.hpp index 016009ab..aa040278 100644 --- a/include/cru/win/graph/direct/TextLayout.hpp +++ b/include/cru/win/graphics/direct/TextLayout.hpp @@ -2,12 +2,12 @@ #include "ComResource.hpp" #include "Resource.hpp" -#include "cru/platform/graph/TextLayout.hpp" +#include "cru/platform/graphics/TextLayout.hpp" #include <limits> #include <memory> -namespace cru::platform::graph::win::direct { +namespace cru::platform::graphics::win::direct { class DWriteFont; class DWriteTextLayout : public DirectGraphResource, @@ -38,7 +38,7 @@ class DWriteTextLayout : public DirectGraphResource, void SetMaxWidth(float max_width) override; void SetMaxHeight(float max_height) override; - Rect GetTextBounds() override; + Rect GetTextBounds(bool includingTrailingSpace = false) override; // Return empty vector if text_range.count is 0. Text range could be in // reverse direction, it should be normalized first in implementation. std::vector<Rect> TextRangeRect(const TextRange& text_range) override; @@ -52,4 +52,4 @@ class DWriteTextLayout : public DirectGraphResource, float max_height_ = std::numeric_limits<float>::max(); Microsoft::WRL::ComPtr<IDWriteTextLayout> text_layout_; }; -} // namespace cru::platform::graph::win::direct +} // namespace cru::platform::graphics::win::direct diff --git a/include/cru/win/graphics/direct/WindowPainter.hpp b/include/cru/win/graphics/direct/WindowPainter.hpp new file mode 100644 index 00000000..b5faf7b5 --- /dev/null +++ b/include/cru/win/graphics/direct/WindowPainter.hpp @@ -0,0 +1,21 @@ +#pragma once +#include "Painter.hpp" +#include "WindowRenderTarget.hpp" + +namespace cru::platform::graphics::win::direct { +class D2DWindowPainter : public graphics::win::direct::D2DPainter { + public: + explicit D2DWindowPainter(D2DWindowRenderTarget* window); + + CRU_DELETE_COPY(D2DWindowPainter) + CRU_DELETE_MOVE(D2DWindowPainter) + + ~D2DWindowPainter() override; + + protected: + void DoEndDraw() override; + + private: + D2DWindowRenderTarget* render_target_; +}; +} // namespace cru::platform::graphics::win::direct diff --git a/include/cru/win/graphics/direct/WindowRenderTarget.hpp b/include/cru/win/graphics/direct/WindowRenderTarget.hpp new file mode 100644 index 00000000..75b1bf20 --- /dev/null +++ b/include/cru/win/graphics/direct/WindowRenderTarget.hpp @@ -0,0 +1,42 @@ +#pragma once +#include "Factory.hpp" + +namespace cru::platform::graphics::win::direct { +// Represents a window render target. +class D2DWindowRenderTarget : public Object { + public: + D2DWindowRenderTarget(gsl::not_null<DirectGraphFactory*> factory, HWND hwnd); + + CRU_DELETE_COPY(D2DWindowRenderTarget) + CRU_DELETE_MOVE(D2DWindowRenderTarget) + + ~D2DWindowRenderTarget() override = default; + + public: + graphics::win::direct::DirectGraphFactory* GetDirectFactory() const { + return factory_; + } + + ID2D1DeviceContext* GetD2D1DeviceContext() { + return d2d1_device_context_.Get(); + } + + void SetDpi(float x, float y); + + // Resize the underlying buffer. + void ResizeBuffer(int width, int height); + + // Present the data of the underlying buffer to the window. + void Present(); + + private: + void CreateTargetBitmap(); + + private: + DirectGraphFactory* factory_; + HWND hwnd_; + Microsoft::WRL::ComPtr<ID2D1DeviceContext> d2d1_device_context_; + Microsoft::WRL::ComPtr<IDXGISwapChain1> dxgi_swap_chain_; + Microsoft::WRL::ComPtr<ID2D1Bitmap1> target_bitmap_; +}; +} // namespace cru::platform::graphics::win::direct diff --git a/include/cru/win/native/Base.hpp b/include/cru/win/gui/Base.hpp index a50c6dd1..00782663 100644 --- a/include/cru/win/native/Base.hpp +++ b/include/cru/win/gui/Base.hpp @@ -3,17 +3,14 @@ #include "cru/common/Base.hpp" -namespace cru::platform::native::win { +namespace cru::platform::gui::win { class GodWindow; class TimerManager; class WinCursor; class WinCursorManager; class WindowClass; class WindowManager; -class WindowRenderTarget; class WinNativeWindow; -class WinNativeWindowResolver; class WinUiApplication; -class WinInputMethodManager; -class WinInputMethodContextRef; -} // namespace cru::platform::native::win +class WinInputMethodContext; +} // namespace cru::platform::gui::win diff --git a/include/cru/win/native/Cursor.hpp b/include/cru/win/gui/Cursor.hpp index 373b9170..e7c76879 100644 --- a/include/cru/win/native/Cursor.hpp +++ b/include/cru/win/gui/Cursor.hpp @@ -1,13 +1,13 @@ #pragma once #include "Resource.hpp" -#include "cru/platform/native/Cursor.hpp" +#include "cru/platform/gui/Cursor.hpp" #include <memory> -namespace cru::platform::native::win { +namespace cru::platform::gui::win { class WinCursor : public WinNativeResource, public virtual ICursor { - CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::native::win::WinCursor") + CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::gui::win::WinCursor") public: WinCursor(HCURSOR handle, bool auto_destroy); @@ -45,5 +45,6 @@ class WinCursorManager : public WinNativeResource, private: std::shared_ptr<WinCursor> sys_arrow_; std::shared_ptr<WinCursor> sys_hand_; + std::shared_ptr<WinCursor> sys_ibeam_; }; -} // namespace cru::platform::native::win +} // namespace cru::platform::gui::win diff --git a/include/cru/win/native/Exception.hpp b/include/cru/win/gui/Exception.hpp index 6a5265c1..895e6c14 100644 --- a/include/cru/win/native/Exception.hpp +++ b/include/cru/win/gui/Exception.hpp @@ -1,7 +1,7 @@ #pragma once #include "../Exception.hpp" -namespace cru::platform::native::win { +namespace cru::platform::gui::win { using platform::win::Win32Error; using platform::win::HResultError; -} // namespace cru::platform::native::win +} // namespace cru::platform::gui::win diff --git a/include/cru/win/native/GodWindow.hpp b/include/cru/win/gui/GodWindow.hpp index 8b20e01f..0343b159 100644 --- a/include/cru/win/native/GodWindow.hpp +++ b/include/cru/win/gui/GodWindow.hpp @@ -1,11 +1,14 @@ #pragma once #include "Base.hpp" +#include "WindowNativeMessageEventArgs.hpp" +#include "cru/common/Event.hpp" + #include <memory> -namespace cru::platform::native::win { +namespace cru::platform::gui::win { class GodWindow : public Object { - CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::native::win::GodWindow") + CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::gui::win::GodWindow") public: explicit GodWindow(WinUiApplication* application); @@ -20,10 +23,16 @@ class GodWindow : public Object { bool HandleGodWindowMessage(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param, LRESULT* result); + IEvent<WindowNativeMessageEventArgs&>* MessageEvent() { + return &message_event_; + } + private: WinUiApplication* application_; std::unique_ptr<WindowClass> god_window_class_; HWND hwnd_; + + Event<WindowNativeMessageEventArgs&> message_event_; }; -} // namespace cru::platform::native::win +} // namespace cru::platform::gui::win diff --git a/include/cru/win/native/InputMethod.hpp b/include/cru/win/gui/InputMethod.hpp index 113f460d..51a007d8 100644 --- a/include/cru/win/native/InputMethod.hpp +++ b/include/cru/win/gui/InputMethod.hpp @@ -6,13 +6,13 @@ #include "Resource.hpp" #include "WindowNativeMessageEventArgs.hpp" -#include "cru/platform/native/InputMethod.hpp" +#include "cru/platform/gui/InputMethod.hpp" #include <imm.h> -namespace cru::platform::native::win { +namespace cru::platform::gui::win { class AutoHIMC : public Object { - CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::native::win::AutoHIMC") + CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::gui::win::AutoHIMC") public: explicit AutoHIMC(HWND hwnd); @@ -35,7 +35,7 @@ class AutoHIMC : public Object { class WinInputMethodContext : public WinNativeResource, public virtual IInputMethodContext { - CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::native::win::WinInputMethodContext") + CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::gui::win::WinInputMethodContext") public: WinInputMethodContext(gsl::not_null<WinNativeWindow*> window); @@ -72,31 +72,16 @@ class WinInputMethodContext : public WinNativeResource, std::u16string GetResultString(); - std::optional<AutoHIMC> TryGetHIMC(); + AutoHIMC GetHIMC(); private: - std::shared_ptr<INativeWindowResolver> native_window_resolver_; + WinNativeWindow* native_window_; - std::vector<EventRevokerGuard> event_revoker_guards_; + EventRevokerListGuard event_guard_; Event<std::nullptr_t> composition_start_event_; Event<std::nullptr_t> composition_end_event_; Event<std::nullptr_t> composition_event_; Event<std::u16string_view> text_event_; }; - -class WinInputMethodManager : public WinNativeResource, - public virtual IInputMethodManager { - public: - WinInputMethodManager(WinUiApplication* application); - - CRU_DELETE_COPY(WinInputMethodManager) - CRU_DELETE_MOVE(WinInputMethodManager) - - ~WinInputMethodManager() override; - - public: - std::unique_ptr<IInputMethodContext> GetContext( - INativeWindow* window) override; -}; -} // namespace cru::platform::native::win +} // namespace cru::platform::gui::win diff --git a/include/cru/win/gui/Keyboard.hpp b/include/cru/win/gui/Keyboard.hpp new file mode 100644 index 00000000..5b98833c --- /dev/null +++ b/include/cru/win/gui/Keyboard.hpp @@ -0,0 +1,9 @@ +#pragma once +#include "Base.hpp" + +#include "cru/platform/gui/Keyboard.hpp" + +namespace cru::platform::gui::win { +KeyCode VirtualKeyToKeyCode(int virtual_key); +KeyModifier RetrieveKeyMofifier(); +} // namespace cru::platform::gui::win diff --git a/include/cru/win/native/Resource.hpp b/include/cru/win/gui/Resource.hpp index 0de0e1a8..1f6f0a4a 100644 --- a/include/cru/win/native/Resource.hpp +++ b/include/cru/win/gui/Resource.hpp @@ -3,7 +3,7 @@ #include "cru/platform/Resource.hpp" -namespace cru::platform::native::win { +namespace cru::platform::gui::win { class WinNativeResource : public Object, public virtual INativeResource { public: static constexpr std::u16string_view k_platform_id = u"Windows"; @@ -20,4 +20,4 @@ class WinNativeResource : public Object, public virtual INativeResource { public: std::u16string_view GetPlatformId() const final { return k_platform_id; } }; -} // namespace cru::platform::native::win +} // namespace cru::platform::gui::win diff --git a/include/cru/win/native/UiApplication.hpp b/include/cru/win/gui/UiApplication.hpp index cbc08af7..4cf46858 100644 --- a/include/cru/win/native/UiApplication.hpp +++ b/include/cru/win/gui/UiApplication.hpp @@ -1,15 +1,16 @@ #pragma once #include "Resource.hpp" -#include "cru/platform/native/UiApplication.hpp" +#include "cru/platform/gui/Base.hpp" +#include "cru/platform/gui/UiApplication.hpp" #include <memory> -namespace cru::platform::graph::win::direct { +namespace cru::platform::graphics::win::direct { class DirectGraphFactory; } -namespace cru::platform::native::win { +namespace cru::platform::gui::win { class WinUiApplication : public WinNativeResource, public virtual IUiApplication { public: @@ -32,7 +33,7 @@ class WinUiApplication : public WinNativeResource, void AddOnQuitHandler(std::function<void()> handler) override; - void InvokeLater(std::function<void()> action) override; + long long SetImmediate(std::function<void()> action) override; long long SetTimeout(std::chrono::milliseconds milliseconds, std::function<void()> action) override; long long SetInterval(std::chrono::milliseconds milliseconds, @@ -40,17 +41,15 @@ class WinUiApplication : public WinNativeResource, void CancelTimer(long long id) override; std::vector<INativeWindow*> GetAllWindow() override; - std::shared_ptr<INativeWindowResolver> CreateWindow( - INativeWindow* parent) override; + INativeWindow* CreateWindow(INativeWindow* parent, CreateWindowFlag flag) override; - cru::platform::graph::IGraphFactory* GetGraphFactory() override; + cru::platform::graphics::IGraphFactory* GetGraphFactory() override; - cru::platform::graph::win::direct::DirectGraphFactory* GetDirectFactory() { + cru::platform::graphics::win::direct::DirectGraphFactory* GetDirectFactory() { return graph_factory_.get(); } ICursorManager* GetCursorManager() override; - IInputMethodManager* GetInputMethodManager() override; HINSTANCE GetInstanceHandle() const { return instance_handle_; } @@ -61,7 +60,7 @@ class WinUiApplication : public WinNativeResource, private: HINSTANCE instance_handle_; - std::unique_ptr<cru::platform::graph::win::direct::DirectGraphFactory> + std::unique_ptr<cru::platform::graphics::win::direct::DirectGraphFactory> graph_factory_; std::unique_ptr<GodWindow> god_window_; @@ -69,8 +68,7 @@ class WinUiApplication : public WinNativeResource, std::unique_ptr<WindowManager> window_manager_; std::unique_ptr<WinCursorManager> cursor_manager_; - std::unique_ptr<WinInputMethodManager> input_method_manager_; std::vector<std::function<void()>> quit_handlers_; }; -} // namespace cru::platform::native::win +} // namespace cru::platform::gui::win diff --git a/include/cru/win/native/Window.hpp b/include/cru/win/gui/Window.hpp index 3e0b11cd..3ba9ef68 100644 --- a/include/cru/win/native/Window.hpp +++ b/include/cru/win/gui/Window.hpp @@ -2,13 +2,16 @@ #include "Resource.hpp" #include "WindowNativeMessageEventArgs.hpp" -#include "cru/platform/native/Window.hpp" +#include "cru/platform/GraphBase.hpp" +#include "cru/platform/gui/Base.hpp" +#include "cru/platform/gui/Window.hpp" +#include "cru/win/graphics/direct/WindowRenderTarget.hpp" #include <memory> -namespace cru::platform::native::win { +namespace cru::platform::gui::win { class WinNativeWindow : public WinNativeResource, public virtual INativeWindow { - CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::native::win::WinNativeWindow") + CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::gui::win::WinNativeWindow") public: WinNativeWindow(WinUiApplication* application, WindowClass* window_class, @@ -20,10 +23,6 @@ class WinNativeWindow : public WinNativeResource, public virtual INativeWindow { ~WinNativeWindow() override; public: - std::shared_ptr<INativeWindowResolver> GetResolver() override { - return std::static_pointer_cast<INativeWindowResolver>(resolver_); - } - void Close() override; WinNativeWindow* GetParent() override { return parent_window_; } @@ -48,7 +47,7 @@ class WinNativeWindow : public WinNativeResource, public virtual INativeWindow { bool ReleaseMouse() override; void RequestRepaint() override; - std::unique_ptr<graph::IPainter> BeginPaint() override; + std::unique_ptr<graphics::IPainter> BeginPaint() override; void SetCursor(std::shared_ptr<ICursor> cursor) override; @@ -60,18 +59,18 @@ class WinNativeWindow : public WinNativeResource, public virtual INativeWindow { return &mouse_enter_leave_event_; } IEvent<Point>* MouseMoveEvent() override { return &mouse_move_event_; } - IEvent<platform::native::NativeMouseButtonEventArgs>* MouseDownEvent() + IEvent<platform::gui::NativeMouseButtonEventArgs>* MouseDownEvent() override { return &mouse_down_event_; } - IEvent<platform::native::NativeMouseButtonEventArgs>* MouseUpEvent() + IEvent<platform::gui::NativeMouseButtonEventArgs>* MouseUpEvent() override { return &mouse_up_event_; } - IEvent<platform::native::NativeKeyEventArgs>* KeyDownEvent() override { + IEvent<platform::gui::NativeKeyEventArgs>* KeyDownEvent() override { return &key_down_event_; } - IEvent<platform::native::NativeKeyEventArgs>* KeyUpEvent() override { + IEvent<platform::gui::NativeKeyEventArgs>* KeyUpEvent() override { return &key_up_event_; } @@ -79,16 +78,40 @@ class WinNativeWindow : public WinNativeResource, public virtual INativeWindow { return &native_message_event_; } + IInputMethodContext* GetInputMethodContext() override; + // Get the handle of the window. Return null if window is invalid. HWND GetWindowHandle() const { return hwnd_; } bool HandleNativeWindowMessage(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param, LRESULT* result); - WindowRenderTarget* GetWindowRenderTarget() const { + graphics::win::direct::D2DWindowRenderTarget* GetWindowRenderTarget() const { return window_render_target_.get(); } + //*************** region: dpi *************** + float GetDpi() const { return dpi_; } + + inline int DipToPixel(const float dip) { + return static_cast<int>(dip * GetDpi() / 96.0f); + } + + inline POINT DipToPixel(const Point& dip_point) { + POINT result; + result.x = DipToPixel(dip_point.x); + result.y = DipToPixel(dip_point.y); + return result; + } + + inline float PixelToDip(const int pixel) { + return static_cast<float>(pixel) * 96.0f / GetDpi(); + } + + inline Point PixelToDip(const POINT& pi_point) { + return Point(PixelToDip(pi_point.x), PixelToDip(pi_point.y)); + } + private: // Get the client rect in pixel. RECT GetClientRectPixel(); @@ -104,8 +127,8 @@ class WinNativeWindow : public WinNativeResource, public virtual INativeWindow { void OnMouseMoveInternal(POINT point); void OnMouseLeaveInternal(); - void OnMouseDownInternal(platform::native::MouseButton button, POINT point); - void OnMouseUpInternal(platform::native::MouseButton button, POINT point); + void OnMouseDownInternal(platform::gui::MouseButton button, POINT point); + void OnMouseUpInternal(platform::gui::MouseButton button, POINT point); void OnMouseWheelInternal(short delta, POINT point); void OnKeyDownInternal(int virtual_code); @@ -124,53 +147,32 @@ class WinNativeWindow : public WinNativeResource, public virtual INativeWindow { // again. bool sync_flag_ = false; - std::shared_ptr<WinNativeWindowResolver> resolver_; - HWND hwnd_; WinNativeWindow* parent_window_; + float dpi_; + bool has_focus_ = false; bool is_mouse_in_ = false; - std::unique_ptr<WindowRenderTarget> window_render_target_; + std::unique_ptr<graphics::win::direct::D2DWindowRenderTarget> + window_render_target_; std::shared_ptr<WinCursor> cursor_; + std::unique_ptr<WinInputMethodContext> input_method_context_; + Event<std::nullptr_t> destroy_event_; Event<std::nullptr_t> paint_event_; Event<Size> resize_event_; Event<FocusChangeType> focus_event_; Event<MouseEnterLeaveType> mouse_enter_leave_event_; Event<Point> mouse_move_event_; - Event<platform::native::NativeMouseButtonEventArgs> mouse_down_event_; - Event<platform::native::NativeMouseButtonEventArgs> mouse_up_event_; - Event<platform::native::NativeKeyEventArgs> key_down_event_; - Event<platform::native::NativeKeyEventArgs> key_up_event_; + Event<platform::gui::NativeMouseButtonEventArgs> mouse_down_event_; + Event<platform::gui::NativeMouseButtonEventArgs> mouse_up_event_; + Event<platform::gui::NativeKeyEventArgs> key_down_event_; + Event<platform::gui::NativeKeyEventArgs> key_up_event_; Event<WindowNativeMessageEventArgs&> native_message_event_; }; - -class WinNativeWindowResolver : public WinNativeResource, - public virtual INativeWindowResolver { - friend WinNativeWindow::~WinNativeWindow(); - - public: - WinNativeWindowResolver(WinNativeWindow* window) : window_(window) {} - - CRU_DELETE_COPY(WinNativeWindowResolver) - CRU_DELETE_MOVE(WinNativeWindowResolver) - - ~WinNativeWindowResolver() override = default; - - public: - INativeWindow* Resolve() override { return window_; } - - private: - void Reset(); - - private: - WinNativeWindow* window_; -}; - -WinNativeWindow* Resolve(gsl::not_null<INativeWindowResolver*> resolver); -} // namespace cru::platform::native::win +} // namespace cru::platform::gui::win diff --git a/include/cru/win/native/WindowClass.hpp b/include/cru/win/gui/WindowClass.hpp index fdd55065..2c07b68f 100644 --- a/include/cru/win/native/WindowClass.hpp +++ b/include/cru/win/gui/WindowClass.hpp @@ -3,7 +3,7 @@ #include <string> -namespace cru::platform::native::win { +namespace cru::platform::gui::win { class WindowClass : public Object { public: WindowClass(std::wstring name, WNDPROC window_proc, HINSTANCE h_instance); @@ -21,4 +21,4 @@ class WindowClass : public Object { std::wstring name_; ATOM atom_; }; -} // namespace cru::platform::native::win +} // namespace cru::platform::gui::win diff --git a/include/cru/win/native/WindowNativeMessageEventArgs.hpp b/include/cru/win/gui/WindowNativeMessageEventArgs.hpp index 84a7a123..834ba3c2 100644 --- a/include/cru/win/native/WindowNativeMessageEventArgs.hpp +++ b/include/cru/win/gui/WindowNativeMessageEventArgs.hpp @@ -3,7 +3,7 @@ #include "cru/common/Base.hpp" -namespace cru::platform::native::win { +namespace cru::platform::gui::win { struct WindowNativeMessage { HWND hwnd; UINT msg; @@ -37,4 +37,4 @@ class WindowNativeMessageEventArgs : public Object { LRESULT result_; bool handled_ = false; }; -} // namespace cru::platform::native::win +} // namespace cru::platform::gui::win diff --git a/include/cru/win/native/Keyboard.hpp b/include/cru/win/native/Keyboard.hpp deleted file mode 100644 index 790e0015..00000000 --- a/include/cru/win/native/Keyboard.hpp +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once -#include "Base.hpp" - -#include "cru/platform/native/Keyboard.hpp" - -namespace cru::platform::native::win { -KeyCode VirtualKeyToKeyCode(int virtual_key); -KeyModifier RetrieveKeyMofifier(); -} // namespace cru::platform::native::win diff --git a/include/cru/win/native/WindowRenderTarget.hpp b/include/cru/win/native/WindowRenderTarget.hpp deleted file mode 100644 index 83ac1e03..00000000 --- a/include/cru/win/native/WindowRenderTarget.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once -#include "Base.hpp" - -namespace cru::platform::graph::win::direct { -class DirectGraphFactory; -} - -namespace cru::platform::native::win { -// Represents a window render target. -class WindowRenderTarget : public Object { - public: - WindowRenderTarget(graph::win::direct::DirectGraphFactory* factory, - HWND hwnd); - - CRU_DELETE_COPY(WindowRenderTarget) - CRU_DELETE_MOVE(WindowRenderTarget) - - ~WindowRenderTarget() override = default; - - public: - graph::win::direct::DirectGraphFactory* GetDirectFactory() const { - return factory_; - } - - ID2D1DeviceContext* GetD2D1DeviceContext() { - return d2d1_device_context_.Get(); - } - - // Resize the underlying buffer. - void ResizeBuffer(int width, int height); - - // Set this render target as the d2d device context's target. - void SetAsTarget(); - - // Present the data of the underlying buffer to the window. - void Present(); - - private: - void CreateTargetBitmap(); - - private: - graph::win::direct::DirectGraphFactory* factory_; - Microsoft::WRL::ComPtr<ID2D1DeviceContext> d2d1_device_context_; - Microsoft::WRL::ComPtr<IDXGISwapChain1> dxgi_swap_chain_; - Microsoft::WRL::ComPtr<ID2D1Bitmap1> target_bitmap_; -}; -} // namespace cru::platform::native::win |