From 06d1d0442276a05b6caad6e3468f4afb1e8ee5df Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 28 Jun 2020 00:03:11 +0800 Subject: ... --- include/cru/common/Base.hpp | 47 +++ include/cru/common/Bitmask.hpp | 42 +++ include/cru/common/Event.hpp | 213 ++++++++++++++ include/cru/common/Logger.hpp | 92 ++++++ include/cru/common/base.hpp | 47 --- include/cru/common/bitmask.hpp | 42 --- include/cru/common/event.hpp | 213 -------------- include/cru/common/logger.hpp | 92 ------ include/cru/platform/Check.hpp | 40 +++ include/cru/platform/Exception.hpp | 28 ++ include/cru/platform/Matrix.hpp | 83 ++++++ include/cru/platform/Resource.hpp | 10 + include/cru/platform/check.hpp | 40 --- include/cru/platform/exception.hpp | 28 -- include/cru/platform/graph/Base.hpp | 24 ++ include/cru/platform/graph/Brush.hpp | 11 + include/cru/platform/graph/Factory.hpp | 25 ++ include/cru/platform/graph/Font.hpp | 8 + include/cru/platform/graph/Geometry.hpp | 20 ++ include/cru/platform/graph/Painter.hpp | 29 ++ include/cru/platform/graph/Resource.hpp | 10 + include/cru/platform/graph/base.hpp | 24 -- include/cru/platform/graph/brush.hpp | 11 - include/cru/platform/graph/factory.hpp | 25 -- include/cru/platform/graph/font.hpp | 8 - include/cru/platform/graph/geometry.hpp | 20 -- include/cru/platform/graph/painter.hpp | 29 -- include/cru/platform/graph/resource.hpp | 10 - include/cru/platform/graph/util/Painter.hpp | 17 ++ include/cru/platform/graph/util/painter.hpp | 17 -- include/cru/platform/matrix.hpp | 83 ------ include/cru/platform/native/Base.hpp | 52 ++++ include/cru/platform/native/Cursor.hpp | 15 + include/cru/platform/native/Keyboard.hpp | 120 ++++++++ include/cru/platform/native/Window.hpp | 67 +++++ include/cru/platform/native/base.hpp | 52 ---- include/cru/platform/native/cursor.hpp | 15 - include/cru/platform/native/keyboard.hpp | 120 -------- include/cru/platform/native/window.hpp | 67 ----- include/cru/platform/resource.hpp | 10 - include/cru/ui/Base.hpp | 281 ++++++++++++++++++ include/cru/ui/Control.hpp | 152 ++++++++++ include/cru/ui/Window.hpp | 40 +++ include/cru/ui/base.hpp | 281 ------------------ include/cru/ui/control.hpp | 152 ---------- include/cru/ui/controls/Base.hpp | 24 ++ include/cru/ui/controls/Button.hpp | 42 +++ include/cru/ui/controls/Container.hpp | 28 ++ include/cru/ui/controls/base.hpp | 24 -- include/cru/ui/controls/button.hpp | 42 --- include/cru/ui/controls/container.hpp | 28 -- include/cru/ui/render/Base.hpp | 12 + include/cru/ui/render/base.hpp | 12 - include/cru/ui/window.hpp | 40 --- include/cru/win/Exception.hpp | 52 ++++ include/cru/win/String.hpp | 107 +++++++ include/cru/win/exception.hpp | 52 ---- include/cru/win/graph/direct/Brush.hpp | 39 +++ include/cru/win/graph/direct/Exception.hpp | 7 + include/cru/win/graph/direct/Factory.hpp | 58 ++++ include/cru/win/graph/direct/Font.hpp | 32 ++ include/cru/win/graph/direct/Geometry.hpp | 57 ++++ include/cru/win/graph/direct/Painter.hpp | 60 ++++ include/cru/win/graph/direct/Resource.hpp | 49 ++++ include/cru/win/graph/direct/brush.hpp | 39 --- include/cru/win/graph/direct/exception.hpp | 7 - include/cru/win/graph/direct/factory.hpp | 58 ---- include/cru/win/graph/direct/font.hpp | 32 -- include/cru/win/graph/direct/geometry.hpp | 57 ---- include/cru/win/graph/direct/painter.hpp | 60 ---- include/cru/win/graph/direct/resource.hpp | 49 ---- include/cru/win/native/Base.hpp | 19 ++ include/cru/win/native/Cursor.hpp | 47 +++ include/cru/win/native/Exception.hpp | 7 + include/cru/win/native/Keyboard.hpp | 9 + include/cru/win/native/Resource.hpp | 23 ++ include/cru/win/native/Window.hpp | 174 +++++++++++ include/cru/win/native/base.hpp | 19 -- include/cru/win/native/cursor.hpp | 47 --- include/cru/win/native/exception.hpp | 7 - include/cru/win/native/keyboard.hpp | 9 - include/cru/win/native/resource.hpp | 23 -- include/cru/win/native/window.hpp | 174 ----------- include/cru/win/string.hpp | 107 ------- src/common/Logger.cpp | 65 ++++ src/common/logger.cpp | 65 ---- src/ui/Control.cpp | 130 ++++++++ src/ui/Helper.cpp | 15 + src/ui/Helper.hpp | 17 ++ src/ui/Window.cpp | 28 ++ src/ui/control.cpp | 130 -------- src/ui/controls/Button.cpp | 73 +++++ src/ui/controls/Container.cpp | 18 ++ src/ui/controls/button.cpp | 73 ----- src/ui/controls/container.cpp | 18 -- src/ui/helper.cpp | 15 - src/ui/helper.hpp | 17 -- src/ui/window.cpp | 28 -- src/win/Exception.cpp | 38 +++ src/win/String.cpp | 188 ++++++++++++ src/win/exception.cpp | 38 --- src/win/graph/direct/Brush.cpp | 17 ++ src/win/graph/direct/Factory.cpp | 107 +++++++ src/win/graph/direct/Font.cpp | 33 +++ src/win/graph/direct/Geometry.cpp | 62 ++++ src/win/graph/direct/Painter.cpp | 104 +++++++ src/win/graph/direct/Resource.cpp | 12 + src/win/graph/direct/brush.cpp | 17 -- src/win/graph/direct/factory.cpp | 107 ------- src/win/graph/direct/font.cpp | 33 --- src/win/graph/direct/geometry.cpp | 62 ---- src/win/graph/direct/painter.cpp | 104 ------- src/win/graph/direct/resource.cpp | 12 - src/win/native/Cursor.cpp | 51 ++++ src/win/native/Keyboard.cpp | 74 +++++ src/win/native/Timer.cpp | 28 ++ src/win/native/Timer.hpp | 34 +++ src/win/native/Window.cpp | 441 ++++++++++++++++++++++++++++ src/win/native/cursor.cpp | 51 ---- src/win/native/keyboard.cpp | 74 ----- src/win/native/timer.cpp | 28 -- src/win/native/timer.hpp | 34 --- src/win/native/window.cpp | 441 ---------------------------- src/win/string.cpp | 188 ------------ test/win/String.cpp | 53 ++++ test/win/string.cpp | 53 ---- 126 files changed, 3860 insertions(+), 3860 deletions(-) create mode 100644 include/cru/common/Base.hpp create mode 100644 include/cru/common/Bitmask.hpp create mode 100644 include/cru/common/Event.hpp create mode 100644 include/cru/common/Logger.hpp delete mode 100644 include/cru/common/base.hpp delete mode 100644 include/cru/common/bitmask.hpp delete mode 100644 include/cru/common/event.hpp delete mode 100644 include/cru/common/logger.hpp create mode 100644 include/cru/platform/Check.hpp create mode 100644 include/cru/platform/Exception.hpp create mode 100644 include/cru/platform/Matrix.hpp create mode 100644 include/cru/platform/Resource.hpp delete mode 100644 include/cru/platform/check.hpp delete mode 100644 include/cru/platform/exception.hpp create mode 100644 include/cru/platform/graph/Base.hpp create mode 100644 include/cru/platform/graph/Brush.hpp create mode 100644 include/cru/platform/graph/Factory.hpp create mode 100644 include/cru/platform/graph/Font.hpp create mode 100644 include/cru/platform/graph/Geometry.hpp create mode 100644 include/cru/platform/graph/Painter.hpp create mode 100644 include/cru/platform/graph/Resource.hpp delete mode 100644 include/cru/platform/graph/base.hpp delete mode 100644 include/cru/platform/graph/brush.hpp delete mode 100644 include/cru/platform/graph/factory.hpp delete mode 100644 include/cru/platform/graph/font.hpp delete mode 100644 include/cru/platform/graph/geometry.hpp delete mode 100644 include/cru/platform/graph/painter.hpp delete mode 100644 include/cru/platform/graph/resource.hpp create mode 100644 include/cru/platform/graph/util/Painter.hpp delete mode 100644 include/cru/platform/graph/util/painter.hpp delete mode 100644 include/cru/platform/matrix.hpp create mode 100644 include/cru/platform/native/Base.hpp create mode 100644 include/cru/platform/native/Cursor.hpp create mode 100644 include/cru/platform/native/Keyboard.hpp create mode 100644 include/cru/platform/native/Window.hpp delete mode 100644 include/cru/platform/native/base.hpp delete mode 100644 include/cru/platform/native/cursor.hpp delete mode 100644 include/cru/platform/native/keyboard.hpp delete mode 100644 include/cru/platform/native/window.hpp delete mode 100644 include/cru/platform/resource.hpp create mode 100644 include/cru/ui/Base.hpp create mode 100644 include/cru/ui/Control.hpp create mode 100644 include/cru/ui/Window.hpp delete mode 100644 include/cru/ui/base.hpp delete mode 100644 include/cru/ui/control.hpp create mode 100644 include/cru/ui/controls/Base.hpp create mode 100644 include/cru/ui/controls/Button.hpp create mode 100644 include/cru/ui/controls/Container.hpp delete mode 100644 include/cru/ui/controls/base.hpp delete mode 100644 include/cru/ui/controls/button.hpp delete mode 100644 include/cru/ui/controls/container.hpp create mode 100644 include/cru/ui/render/Base.hpp delete mode 100644 include/cru/ui/render/base.hpp delete mode 100644 include/cru/ui/window.hpp create mode 100644 include/cru/win/Exception.hpp create mode 100644 include/cru/win/String.hpp delete mode 100644 include/cru/win/exception.hpp create mode 100644 include/cru/win/graph/direct/Brush.hpp create mode 100644 include/cru/win/graph/direct/Exception.hpp create mode 100644 include/cru/win/graph/direct/Factory.hpp create mode 100644 include/cru/win/graph/direct/Font.hpp create mode 100644 include/cru/win/graph/direct/Geometry.hpp create mode 100644 include/cru/win/graph/direct/Painter.hpp create mode 100644 include/cru/win/graph/direct/Resource.hpp delete mode 100644 include/cru/win/graph/direct/brush.hpp delete mode 100644 include/cru/win/graph/direct/exception.hpp delete mode 100644 include/cru/win/graph/direct/factory.hpp delete mode 100644 include/cru/win/graph/direct/font.hpp delete mode 100644 include/cru/win/graph/direct/geometry.hpp delete mode 100644 include/cru/win/graph/direct/painter.hpp delete mode 100644 include/cru/win/graph/direct/resource.hpp create mode 100644 include/cru/win/native/Base.hpp create mode 100644 include/cru/win/native/Cursor.hpp create mode 100644 include/cru/win/native/Exception.hpp create mode 100644 include/cru/win/native/Keyboard.hpp create mode 100644 include/cru/win/native/Resource.hpp create mode 100644 include/cru/win/native/Window.hpp delete mode 100644 include/cru/win/native/base.hpp delete mode 100644 include/cru/win/native/cursor.hpp delete mode 100644 include/cru/win/native/exception.hpp delete mode 100644 include/cru/win/native/keyboard.hpp delete mode 100644 include/cru/win/native/resource.hpp delete mode 100644 include/cru/win/native/window.hpp delete mode 100644 include/cru/win/string.hpp create mode 100644 src/common/Logger.cpp delete mode 100644 src/common/logger.cpp create mode 100644 src/ui/Control.cpp create mode 100644 src/ui/Helper.cpp create mode 100644 src/ui/Helper.hpp create mode 100644 src/ui/Window.cpp delete mode 100644 src/ui/control.cpp create mode 100644 src/ui/controls/Button.cpp create mode 100644 src/ui/controls/Container.cpp delete mode 100644 src/ui/controls/button.cpp delete mode 100644 src/ui/controls/container.cpp delete mode 100644 src/ui/helper.cpp delete mode 100644 src/ui/helper.hpp delete mode 100644 src/ui/window.cpp create mode 100644 src/win/Exception.cpp create mode 100644 src/win/String.cpp delete mode 100644 src/win/exception.cpp create mode 100644 src/win/graph/direct/Brush.cpp create mode 100644 src/win/graph/direct/Factory.cpp create mode 100644 src/win/graph/direct/Font.cpp create mode 100644 src/win/graph/direct/Geometry.cpp create mode 100644 src/win/graph/direct/Painter.cpp create mode 100644 src/win/graph/direct/Resource.cpp delete mode 100644 src/win/graph/direct/brush.cpp delete mode 100644 src/win/graph/direct/factory.cpp delete mode 100644 src/win/graph/direct/font.cpp delete mode 100644 src/win/graph/direct/geometry.cpp delete mode 100644 src/win/graph/direct/painter.cpp delete mode 100644 src/win/graph/direct/resource.cpp create mode 100644 src/win/native/Cursor.cpp create mode 100644 src/win/native/Keyboard.cpp create mode 100644 src/win/native/Timer.cpp create mode 100644 src/win/native/Timer.hpp create mode 100644 src/win/native/Window.cpp delete mode 100644 src/win/native/cursor.cpp delete mode 100644 src/win/native/keyboard.cpp delete mode 100644 src/win/native/timer.cpp delete mode 100644 src/win/native/timer.hpp delete mode 100644 src/win/native/window.cpp delete mode 100644 src/win/string.cpp create mode 100644 test/win/String.cpp delete mode 100644 test/win/string.cpp diff --git a/include/cru/common/Base.hpp b/include/cru/common/Base.hpp new file mode 100644 index 00000000..ff7ab31e --- /dev/null +++ b/include/cru/common/Base.hpp @@ -0,0 +1,47 @@ +#pragma once +#include "PreConfig.hpp" + +#include + +#include + +#define CRU_UNUSED(entity) static_cast(entity); + +#define CRU_DEFAULT_COPY(classname) \ + classname(const classname&) = default; \ + classname& operator=(const classname&) = default; + +#define CRU_DEFAULT_MOVE(classname) \ + classname(classname&&) = default; \ + classname& operator=(classname&&) = default; + +#define CRU_DELETE_COPY(classname) \ + classname(const classname&) = delete; \ + classname& operator=(const classname&) = delete; + +#define CRU_DELETE_MOVE(classname) \ + classname(classname&&) = delete; \ + classname& operator=(classname&&) = delete; + +namespace cru { +class Object { + public: + Object() = default; + CRU_DEFAULT_COPY(Object) + CRU_DEFAULT_MOVE(Object) + virtual ~Object() = default; +}; + +struct Interface { + Interface() = default; + CRU_DELETE_COPY(Interface) + CRU_DELETE_MOVE(Interface) + virtual ~Interface() = default; +}; + +[[noreturn]] inline void UnreachableCode() { + throw std::runtime_error("Unreachable code."); +} + +using Index = gsl::index; +} // namespace cru diff --git a/include/cru/common/Bitmask.hpp b/include/cru/common/Bitmask.hpp new file mode 100644 index 00000000..ddfdc86b --- /dev/null +++ b/include/cru/common/Bitmask.hpp @@ -0,0 +1,42 @@ +#pragma once +#include "Base.hpp" + +namespace cru { +template +struct Bitmask final { + using Underlying = TUnderlying; + + constexpr Bitmask() : value(0) {} + constexpr explicit Bitmask(TUnderlying value) : value(value) {} + + CRU_DEFAULT_COPY(Bitmask) + CRU_DEFAULT_MOVE(Bitmask) + + ~Bitmask() = default; + + Bitmask operator|(Bitmask rhs) const { return Bitmask(value | rhs.value); } + Bitmask operator&(Bitmask rhs) const { return Bitmask(value & rhs.value); } + Bitmask operator^(Bitmask rhs) const { return Bitmask(value ^ rhs.value); } + Bitmask operator~() const { return Bitmask(~value); } + Bitmask& operator|=(Bitmask rhs) { + value |= rhs.value; + return *this; + } + Bitmask& operator&=(Bitmask rhs) { + value &= rhs.value; + return *this; + } + Bitmask& operator^=(Bitmask rhs) { + value ^= rhs.value; + return *this; + } + + bool operator==(Bitmask rhs) const { return this->value == rhs.value; } + bool operator!=(Bitmask rhs) const { return this->value != rhs.value; } + + explicit operator TUnderlying() const { return value; } + explicit operator bool() const { return value != 0; } + + TUnderlying value; +}; +} // namespace cru diff --git a/include/cru/common/Event.hpp b/include/cru/common/Event.hpp new file mode 100644 index 00000000..377ca7f3 --- /dev/null +++ b/include/cru/common/Event.hpp @@ -0,0 +1,213 @@ +#pragma once +#include "Base.hpp" + +#include "SelfResolvable.hpp" + +#include +#include +#include +#include +#include + +namespace cru { +class EventRevoker; + +namespace details { +// Base class of all Event. +// It erases event args types and provides a +// unified form to create event revoker and +// revoke(remove) handler. +class EventBase : public SelfResolvable { + friend EventRevoker; + + protected: + using EventHandlerToken = long; + + EventBase() {} + EventBase(const EventBase& other) = delete; + EventBase(EventBase&& other) = delete; + EventBase& operator=(const EventBase& other) = delete; + EventBase& operator=(EventBase&& other) = delete; + virtual ~EventBase() = default; + + // Remove the handler with the given token. If the token + // corresponds to no handler (which might have be revoked + // before), then nothing will be done. + virtual void RemoveHandler(EventHandlerToken token) = 0; + + // Create a revoker with the given token. + inline EventRevoker CreateRevoker(EventHandlerToken token); +}; +} // namespace details + +// A non-copyable and movable event revoker. +// Call function call operator to revoke the handler. +class EventRevoker { + friend details::EventBase; + + private: + EventRevoker(ObjectResolver&& resolver, + details::EventBase::EventHandlerToken token) + : resolver_(std::move(resolver)), token_(token) {} + + public: + EventRevoker(const EventRevoker& other) = default; + EventRevoker(EventRevoker&& other) = default; + EventRevoker& operator=(const EventRevoker& other) = default; + EventRevoker& operator=(EventRevoker&& other) = default; + ~EventRevoker() = default; + + // Revoke the registered handler. If the event has already + // been destroyed, then nothing will be done. If one of the + // copies calls this, then other copies's calls will have no + // effect. (They have the same token.) + void operator()() const { + if (const auto event = resolver_.Resolve()) { + event->RemoveHandler(token_); + } + } + + private: + ObjectResolver resolver_; + details::EventBase::EventHandlerToken token_; +}; + +inline EventRevoker details::EventBase::CreateRevoker(EventHandlerToken token) { + return EventRevoker(CreateResolver(), token); +} + +// int -> int +// Point -> const Point& +// int& -> int& +template +using DeducedEventArgs = std::conditional_t< + std::is_lvalue_reference_v, TRaw, + std::conditional_t, TRaw, const TRaw&>>; + +// Provides an interface of event. +// IEvent only allow to add handler but not to raise the event. You may +// want to create an Event object and expose IEvent only so users won't +// be able to emit the event. +template +struct IEvent { + public: + using EventArgs = DeducedEventArgs; + using EventHandler = std::function; + + protected: + IEvent() = default; + IEvent(const IEvent& other) = delete; + IEvent(IEvent&& other) = delete; + IEvent& operator=(const IEvent& other) = delete; + IEvent& operator=(IEvent&& other) = delete; + ~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; +}; + +// A non-copyable non-movable Event class. +// It stores a list of event handlers. +template +class Event : public details::EventBase, public IEvent { + using typename IEvent::EventHandler; + + private: + struct HandlerData { + HandlerData(EventHandlerToken token, EventHandler handler) + : token(token), handler(handler) {} + EventHandlerToken token; + EventHandler handler; + }; + + public: + Event() = default; + Event(const Event&) = delete; + Event& operator=(const Event&) = delete; + Event(Event&&) = delete; + Event& operator=(Event&&) = delete; + ~Event() = default; + + EventRevoker AddHandler(const EventHandler& handler) override { + const auto token = current_token_++; + this->handler_data_list_.emplace_back(token, handler); + return CreateRevoker(token); + } + + EventRevoker AddHandler(EventHandler&& handler) override { + const auto token = current_token_++; + this->handler_data_list_.emplace_back(token, std::move(handler)); + return CreateRevoker(token); + } + + // This method will make a copy of all handlers. Because user might delete a + // handler in a handler, which may lead to seg fault as the handler is deleted + // while being executed. + // Thanks to this behavior, all handlers will be taken a snapshot when Raise + // is called, so even if you delete a handler during this period, all handlers + // in the snapshot will be executed. + void Raise(typename IEvent::EventArgs args) { + std::vector handlers; + 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); + } + } + + protected: + void RemoveHandler(EventHandlerToken token) override { + const auto find_result = std::find_if( + this->handler_data_list_.cbegin(), this->handler_data_list_.cend(), + [token](const HandlerData& data) { return data.token == token; }); + if (find_result != this->handler_data_list_.cend()) { + this->handler_data_list_.erase(find_result); + } + } + + private: + std::vector handler_data_list_; + EventHandlerToken current_token_ = 0; +}; + +namespace details { +struct EventRevokerDestroyer { + void operator()(EventRevoker* p) { + (*p)(); + delete p; + } +}; +} // namespace details + +class EventRevokerGuard { + public: + EventRevokerGuard() = default; + explicit EventRevokerGuard(EventRevoker&& revoker) + : revoker_(new EventRevoker(std::move(revoker))) {} + EventRevokerGuard(const EventRevokerGuard& other) = delete; + EventRevokerGuard(EventRevokerGuard&& other) = default; + EventRevokerGuard& operator=(const EventRevokerGuard& other) = delete; + EventRevokerGuard& operator=(EventRevokerGuard&& other) = default; + ~EventRevokerGuard() = default; + + EventRevoker Get() { + // revoker is only null when this is moved + // you shouldn't use a moved instance + Expects(revoker_); + return *revoker_; + } + + void Release() { revoker_.release(); } + + void Reset(EventRevoker&& revoker) { + revoker_.reset(new EventRevoker(std::move(revoker))); + } + + private: + std::unique_ptr revoker_; +}; // namespace cru +} // namespace cru diff --git a/include/cru/common/Logger.hpp b/include/cru/common/Logger.hpp new file mode 100644 index 00000000..bd16678b --- /dev/null +++ b/include/cru/common/Logger.hpp @@ -0,0 +1,92 @@ +#pragma once +#include "cru/common/Base.hpp" + +#include +#include +#include +#include +#include +#include + +namespace cru::log { + +enum class LogLevel { Debug, Info, Warn, Error }; + +struct ILogSource : virtual Interface { + // Write the string s. LogLevel is just a helper. It has no effect on the + // content to write. + virtual void Write(LogLevel level, const std::string_view& s) = 0; +}; + +class StdioLogSource : public virtual ILogSource { + public: + StdioLogSource() = default; + + CRU_DELETE_COPY(StdioLogSource) + CRU_DELETE_MOVE(StdioLogSource) + + ~StdioLogSource() override = default; + + void Write(LogLevel level, const std::string_view& s) override { + // TODO: Emmm... Since it is buggy to use narrow char in UTF-8 on Windows. I + // think this implementation might be broken. (However, I didn't test it.) + // Maybe, I should detect Windows and use wide char (And I didn't test this + // either) or other more complicated implementation. Currently, I settled + // with this. + if (level == LogLevel::Error) { + std::cerr << s; + } else { + std::cout << s; + } + } +}; + +class Logger : public Object { + public: + static Logger* GetInstance(); + + public: + Logger() = default; + + CRU_DELETE_COPY(Logger) + CRU_DELETE_MOVE(Logger) + + ~Logger() override = default; + + public: + void AddSource(std::unique_ptr source); + void RemoveSource(ILogSource* source); + + public: + void Log(LogLevel level, const std::string_view& s); + + public: + std::list> sources_; +}; + +template +void Debug([[maybe_unused]] TArgs&&... args) { +#ifdef CRU_DEBUG + Logger::GetInstance()->Log(LogLevel::Debug, + fmt::format(std::forward(args)...)); +#endif +} + +template +void Info(TArgs&&... args) { + Logger::GetInstance()->Log(LogLevel::Info, + fmt::format(std::forward(args)...)); +} + +template +void Warn(TArgs&&... args) { + Logger::GetInstance()->Log(LogLevel::Warn, + fmt::format(std::forward(args)...)); +} + +template +void Error(TArgs&&... args) { + Logger::GetInstance()->Log(LogLevel::Error, + fmt::format(std::forward(args)...)); +} +} // namespace cru::log diff --git a/include/cru/common/base.hpp b/include/cru/common/base.hpp deleted file mode 100644 index ff7ab31e..00000000 --- a/include/cru/common/base.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once -#include "PreConfig.hpp" - -#include - -#include - -#define CRU_UNUSED(entity) static_cast(entity); - -#define CRU_DEFAULT_COPY(classname) \ - classname(const classname&) = default; \ - classname& operator=(const classname&) = default; - -#define CRU_DEFAULT_MOVE(classname) \ - classname(classname&&) = default; \ - classname& operator=(classname&&) = default; - -#define CRU_DELETE_COPY(classname) \ - classname(const classname&) = delete; \ - classname& operator=(const classname&) = delete; - -#define CRU_DELETE_MOVE(classname) \ - classname(classname&&) = delete; \ - classname& operator=(classname&&) = delete; - -namespace cru { -class Object { - public: - Object() = default; - CRU_DEFAULT_COPY(Object) - CRU_DEFAULT_MOVE(Object) - virtual ~Object() = default; -}; - -struct Interface { - Interface() = default; - CRU_DELETE_COPY(Interface) - CRU_DELETE_MOVE(Interface) - virtual ~Interface() = default; -}; - -[[noreturn]] inline void UnreachableCode() { - throw std::runtime_error("Unreachable code."); -} - -using Index = gsl::index; -} // namespace cru diff --git a/include/cru/common/bitmask.hpp b/include/cru/common/bitmask.hpp deleted file mode 100644 index ddfdc86b..00000000 --- a/include/cru/common/bitmask.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once -#include "Base.hpp" - -namespace cru { -template -struct Bitmask final { - using Underlying = TUnderlying; - - constexpr Bitmask() : value(0) {} - constexpr explicit Bitmask(TUnderlying value) : value(value) {} - - CRU_DEFAULT_COPY(Bitmask) - CRU_DEFAULT_MOVE(Bitmask) - - ~Bitmask() = default; - - Bitmask operator|(Bitmask rhs) const { return Bitmask(value | rhs.value); } - Bitmask operator&(Bitmask rhs) const { return Bitmask(value & rhs.value); } - Bitmask operator^(Bitmask rhs) const { return Bitmask(value ^ rhs.value); } - Bitmask operator~() const { return Bitmask(~value); } - Bitmask& operator|=(Bitmask rhs) { - value |= rhs.value; - return *this; - } - Bitmask& operator&=(Bitmask rhs) { - value &= rhs.value; - return *this; - } - Bitmask& operator^=(Bitmask rhs) { - value ^= rhs.value; - return *this; - } - - bool operator==(Bitmask rhs) const { return this->value == rhs.value; } - bool operator!=(Bitmask rhs) const { return this->value != rhs.value; } - - explicit operator TUnderlying() const { return value; } - explicit operator bool() const { return value != 0; } - - TUnderlying value; -}; -} // namespace cru diff --git a/include/cru/common/event.hpp b/include/cru/common/event.hpp deleted file mode 100644 index 377ca7f3..00000000 --- a/include/cru/common/event.hpp +++ /dev/null @@ -1,213 +0,0 @@ -#pragma once -#include "Base.hpp" - -#include "SelfResolvable.hpp" - -#include -#include -#include -#include -#include - -namespace cru { -class EventRevoker; - -namespace details { -// Base class of all Event. -// It erases event args types and provides a -// unified form to create event revoker and -// revoke(remove) handler. -class EventBase : public SelfResolvable { - friend EventRevoker; - - protected: - using EventHandlerToken = long; - - EventBase() {} - EventBase(const EventBase& other) = delete; - EventBase(EventBase&& other) = delete; - EventBase& operator=(const EventBase& other) = delete; - EventBase& operator=(EventBase&& other) = delete; - virtual ~EventBase() = default; - - // Remove the handler with the given token. If the token - // corresponds to no handler (which might have be revoked - // before), then nothing will be done. - virtual void RemoveHandler(EventHandlerToken token) = 0; - - // Create a revoker with the given token. - inline EventRevoker CreateRevoker(EventHandlerToken token); -}; -} // namespace details - -// A non-copyable and movable event revoker. -// Call function call operator to revoke the handler. -class EventRevoker { - friend details::EventBase; - - private: - EventRevoker(ObjectResolver&& resolver, - details::EventBase::EventHandlerToken token) - : resolver_(std::move(resolver)), token_(token) {} - - public: - EventRevoker(const EventRevoker& other) = default; - EventRevoker(EventRevoker&& other) = default; - EventRevoker& operator=(const EventRevoker& other) = default; - EventRevoker& operator=(EventRevoker&& other) = default; - ~EventRevoker() = default; - - // Revoke the registered handler. If the event has already - // been destroyed, then nothing will be done. If one of the - // copies calls this, then other copies's calls will have no - // effect. (They have the same token.) - void operator()() const { - if (const auto event = resolver_.Resolve()) { - event->RemoveHandler(token_); - } - } - - private: - ObjectResolver resolver_; - details::EventBase::EventHandlerToken token_; -}; - -inline EventRevoker details::EventBase::CreateRevoker(EventHandlerToken token) { - return EventRevoker(CreateResolver(), token); -} - -// int -> int -// Point -> const Point& -// int& -> int& -template -using DeducedEventArgs = std::conditional_t< - std::is_lvalue_reference_v, TRaw, - std::conditional_t, TRaw, const TRaw&>>; - -// Provides an interface of event. -// IEvent only allow to add handler but not to raise the event. You may -// want to create an Event object and expose IEvent only so users won't -// be able to emit the event. -template -struct IEvent { - public: - using EventArgs = DeducedEventArgs; - using EventHandler = std::function; - - protected: - IEvent() = default; - IEvent(const IEvent& other) = delete; - IEvent(IEvent&& other) = delete; - IEvent& operator=(const IEvent& other) = delete; - IEvent& operator=(IEvent&& other) = delete; - ~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; -}; - -// A non-copyable non-movable Event class. -// It stores a list of event handlers. -template -class Event : public details::EventBase, public IEvent { - using typename IEvent::EventHandler; - - private: - struct HandlerData { - HandlerData(EventHandlerToken token, EventHandler handler) - : token(token), handler(handler) {} - EventHandlerToken token; - EventHandler handler; - }; - - public: - Event() = default; - Event(const Event&) = delete; - Event& operator=(const Event&) = delete; - Event(Event&&) = delete; - Event& operator=(Event&&) = delete; - ~Event() = default; - - EventRevoker AddHandler(const EventHandler& handler) override { - const auto token = current_token_++; - this->handler_data_list_.emplace_back(token, handler); - return CreateRevoker(token); - } - - EventRevoker AddHandler(EventHandler&& handler) override { - const auto token = current_token_++; - this->handler_data_list_.emplace_back(token, std::move(handler)); - return CreateRevoker(token); - } - - // This method will make a copy of all handlers. Because user might delete a - // handler in a handler, which may lead to seg fault as the handler is deleted - // while being executed. - // Thanks to this behavior, all handlers will be taken a snapshot when Raise - // is called, so even if you delete a handler during this period, all handlers - // in the snapshot will be executed. - void Raise(typename IEvent::EventArgs args) { - std::vector handlers; - 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); - } - } - - protected: - void RemoveHandler(EventHandlerToken token) override { - const auto find_result = std::find_if( - this->handler_data_list_.cbegin(), this->handler_data_list_.cend(), - [token](const HandlerData& data) { return data.token == token; }); - if (find_result != this->handler_data_list_.cend()) { - this->handler_data_list_.erase(find_result); - } - } - - private: - std::vector handler_data_list_; - EventHandlerToken current_token_ = 0; -}; - -namespace details { -struct EventRevokerDestroyer { - void operator()(EventRevoker* p) { - (*p)(); - delete p; - } -}; -} // namespace details - -class EventRevokerGuard { - public: - EventRevokerGuard() = default; - explicit EventRevokerGuard(EventRevoker&& revoker) - : revoker_(new EventRevoker(std::move(revoker))) {} - EventRevokerGuard(const EventRevokerGuard& other) = delete; - EventRevokerGuard(EventRevokerGuard&& other) = default; - EventRevokerGuard& operator=(const EventRevokerGuard& other) = delete; - EventRevokerGuard& operator=(EventRevokerGuard&& other) = default; - ~EventRevokerGuard() = default; - - EventRevoker Get() { - // revoker is only null when this is moved - // you shouldn't use a moved instance - Expects(revoker_); - return *revoker_; - } - - void Release() { revoker_.release(); } - - void Reset(EventRevoker&& revoker) { - revoker_.reset(new EventRevoker(std::move(revoker))); - } - - private: - std::unique_ptr revoker_; -}; // namespace cru -} // namespace cru diff --git a/include/cru/common/logger.hpp b/include/cru/common/logger.hpp deleted file mode 100644 index bd16678b..00000000 --- a/include/cru/common/logger.hpp +++ /dev/null @@ -1,92 +0,0 @@ -#pragma once -#include "cru/common/Base.hpp" - -#include -#include -#include -#include -#include -#include - -namespace cru::log { - -enum class LogLevel { Debug, Info, Warn, Error }; - -struct ILogSource : virtual Interface { - // Write the string s. LogLevel is just a helper. It has no effect on the - // content to write. - virtual void Write(LogLevel level, const std::string_view& s) = 0; -}; - -class StdioLogSource : public virtual ILogSource { - public: - StdioLogSource() = default; - - CRU_DELETE_COPY(StdioLogSource) - CRU_DELETE_MOVE(StdioLogSource) - - ~StdioLogSource() override = default; - - void Write(LogLevel level, const std::string_view& s) override { - // TODO: Emmm... Since it is buggy to use narrow char in UTF-8 on Windows. I - // think this implementation might be broken. (However, I didn't test it.) - // Maybe, I should detect Windows and use wide char (And I didn't test this - // either) or other more complicated implementation. Currently, I settled - // with this. - if (level == LogLevel::Error) { - std::cerr << s; - } else { - std::cout << s; - } - } -}; - -class Logger : public Object { - public: - static Logger* GetInstance(); - - public: - Logger() = default; - - CRU_DELETE_COPY(Logger) - CRU_DELETE_MOVE(Logger) - - ~Logger() override = default; - - public: - void AddSource(std::unique_ptr source); - void RemoveSource(ILogSource* source); - - public: - void Log(LogLevel level, const std::string_view& s); - - public: - std::list> sources_; -}; - -template -void Debug([[maybe_unused]] TArgs&&... args) { -#ifdef CRU_DEBUG - Logger::GetInstance()->Log(LogLevel::Debug, - fmt::format(std::forward(args)...)); -#endif -} - -template -void Info(TArgs&&... args) { - Logger::GetInstance()->Log(LogLevel::Info, - fmt::format(std::forward(args)...)); -} - -template -void Warn(TArgs&&... args) { - Logger::GetInstance()->Log(LogLevel::Warn, - fmt::format(std::forward(args)...)); -} - -template -void Error(TArgs&&... args) { - Logger::GetInstance()->Log(LogLevel::Error, - fmt::format(std::forward(args)...)); -} -} // namespace cru::log diff --git a/include/cru/platform/Check.hpp b/include/cru/platform/Check.hpp new file mode 100644 index 00000000..f4bbcfe8 --- /dev/null +++ b/include/cru/platform/Check.hpp @@ -0,0 +1,40 @@ +#pragma once +#include "Exception.hpp" +#include "Resource.hpp" + +#include +#include +#include + +namespace cru::platform { +template +TTarget* CheckPlatform(INativeResource* resource, + const std::string_view& target_platform) { + Expects(resource); + const auto result = dynamic_cast(resource); + if (result == nullptr) { + throw UnsupportPlatformException(fmt::format( + "Try to convert resource to target platform failed. Platform id of " + "resource to convert: {} . Target platform id: {} .", + resource->GetPlatformId(), target_platform)); + } + return result; +} + +template +std::shared_ptr CheckPlatform( + const std::shared_ptr& resource, + const std::string_view& target_platform) { + static_assert(std::is_base_of_v, + "TSource must be a subclass of INativeResource."); + Expects(resource); + const auto result = std::dynamic_pointer_cast(resource); + if (result == nullptr) { + throw UnsupportPlatformException(fmt::format( + "Try to convert resource to target platform failed. Platform id of " + "resource to convert: {} . Target platform id: {} .", + resource->GetPlatformId(), target_platform)); + } + return result; +} +} // namespace cru::platform diff --git a/include/cru/platform/Exception.hpp b/include/cru/platform/Exception.hpp new file mode 100644 index 00000000..8b958a1d --- /dev/null +++ b/include/cru/platform/Exception.hpp @@ -0,0 +1,28 @@ +#pragma once +#include "cru/common/Base.hpp" + +#include + +namespace cru::platform { +class PlatformException : public std::runtime_error { + public: + using runtime_error::runtime_error; // inherit constructors +}; + +// This exception is thrown when a resource is used on another platform. +// Of course, you can't mix resources of two different platform. +// For example, Win32 Brush (may add in the future) with Direct Painter. +class UnsupportPlatformException : public std::runtime_error { + public: + using runtime_error::runtime_error; // inherit constructors +}; + +// This exception is thrown when a resource has been disposed and not usable +// again. +// For example, calling Build twice on a GeometryBuilder::Build will lead to +// this exception. +class ReuseException : public std::runtime_error { + public: + using runtime_error::runtime_error; // inherit constructors +}; +} // namespace cru::platform diff --git a/include/cru/platform/Matrix.hpp b/include/cru/platform/Matrix.hpp new file mode 100644 index 00000000..cea5198b --- /dev/null +++ b/include/cru/platform/Matrix.hpp @@ -0,0 +1,83 @@ +#pragma once +#include "GraphBase.hpp" + +#include + +namespace cru::platform { +struct Matrix { + float m11; + float m12; + float m21; + float m22; + float m31; + float m32; + + Matrix() {} + + Matrix(float m11, float m12, float m21, float m22, float m31, float m32) { + this->m11 = m11; + this->m12 = m12; + this->m21 = m21; + this->m22 = m22; + this->m31 = m31; + this->m32 = m32; + } + + bool IsIdentity() const { + return m11 == 1.0f && m12 == 0.0f && m21 == 0.0f && m22 == 1.0f && + m31 == 0.0f && m32 == 0.0f; + } + + Matrix& operator*=(const Matrix& matrix) { + *this = Product(*this, matrix); + return *this; + } + + Matrix operator*(const Matrix& matrix) const { + return Product(*this, matrix); + } + + Point TransformPoint(const Point& point) const { + return Point{point.x * m11 + point.y * m21 + m31, + point.x * m12 + point.y * m22 + m32}; + } + + static Matrix Identity() { + return Matrix{1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f}; + } + + static Matrix Translation(float x, float y) { + return Matrix{1.0f, 0.0f, 0.0f, 1.0f, x, y}; + } + + static Matrix Scale(float sx, float sy) { + return Matrix{sx, 0.0f, 0.0f, sy, 0.0f, 0.0f}; + } + + static Matrix Rotation(float angle) { + float r = AngleToRadian(angle); + float s = std::sinf(r); + float c = std::cosf(r); + + return Matrix{c, s, -s, c, 0.0f, 0.0f}; + } + + static Matrix Skew(float sx, float sy) { + return Matrix{1.0f, sx, sy, 1.0f, 0.0f, 0.0f}; + } + + static Matrix Product(const Matrix& a, const Matrix& b) { + return Matrix{a.m11 * b.m11 + a.m12 * b.m21, + a.m11 * b.m12 + a.m12 * b.m22, + a.m21 * b.m11 + a.m22 * b.m21, + a.m21 * b.m12 + a.m22 * b.m22, + a.m31 * b.m11 + a.m32 * b.m21 + b.m31, + a.m31 * b.m12 + a.m32 * b.m22 + b.m32}; + } + + private: + static constexpr float PI = 3.1415926535f; + + static float AngleToRadian(float angle) { return angle / 180.f * PI; } +}; +} // namespace cru::platform diff --git a/include/cru/platform/Resource.hpp b/include/cru/platform/Resource.hpp new file mode 100644 index 00000000..72cfaf52 --- /dev/null +++ b/include/cru/platform/Resource.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "cru/common/Base.hpp" + +#include + +namespace cru::platform { +struct INativeResource : virtual Interface { + virtual std::string_view GetPlatformId() const = 0; +}; +} // namespace cru::platform diff --git a/include/cru/platform/check.hpp b/include/cru/platform/check.hpp deleted file mode 100644 index f4bbcfe8..00000000 --- a/include/cru/platform/check.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once -#include "Exception.hpp" -#include "Resource.hpp" - -#include -#include -#include - -namespace cru::platform { -template -TTarget* CheckPlatform(INativeResource* resource, - const std::string_view& target_platform) { - Expects(resource); - const auto result = dynamic_cast(resource); - if (result == nullptr) { - throw UnsupportPlatformException(fmt::format( - "Try to convert resource to target platform failed. Platform id of " - "resource to convert: {} . Target platform id: {} .", - resource->GetPlatformId(), target_platform)); - } - return result; -} - -template -std::shared_ptr CheckPlatform( - const std::shared_ptr& resource, - const std::string_view& target_platform) { - static_assert(std::is_base_of_v, - "TSource must be a subclass of INativeResource."); - Expects(resource); - const auto result = std::dynamic_pointer_cast(resource); - if (result == nullptr) { - throw UnsupportPlatformException(fmt::format( - "Try to convert resource to target platform failed. Platform id of " - "resource to convert: {} . Target platform id: {} .", - resource->GetPlatformId(), target_platform)); - } - return result; -} -} // namespace cru::platform diff --git a/include/cru/platform/exception.hpp b/include/cru/platform/exception.hpp deleted file mode 100644 index 8b958a1d..00000000 --- a/include/cru/platform/exception.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include "cru/common/Base.hpp" - -#include - -namespace cru::platform { -class PlatformException : public std::runtime_error { - public: - using runtime_error::runtime_error; // inherit constructors -}; - -// This exception is thrown when a resource is used on another platform. -// Of course, you can't mix resources of two different platform. -// For example, Win32 Brush (may add in the future) with Direct Painter. -class UnsupportPlatformException : public std::runtime_error { - public: - using runtime_error::runtime_error; // inherit constructors -}; - -// This exception is thrown when a resource has been disposed and not usable -// again. -// For example, calling Build twice on a GeometryBuilder::Build will lead to -// this exception. -class ReuseException : public std::runtime_error { - public: - using runtime_error::runtime_error; // inherit constructors -}; -} // namespace cru::platform diff --git a/include/cru/platform/graph/Base.hpp b/include/cru/platform/graph/Base.hpp new file mode 100644 index 00000000..61cfc5ef --- /dev/null +++ b/include/cru/platform/graph/Base.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "../GraphBase.hpp" +#include "../Matrix.hpp" +#include "../Resource.hpp" + +#include + +namespace cru::platform::graph { +// forward declarations +struct IGraphFactory; +struct IBrush; +struct ISolidColorBrush; +struct IFont; +struct IGeometry; +struct IGeometryBuilder; +struct IPainter; +struct ITextLayout; + +struct TextHitTestResult { + int position; + bool trailing; + bool insideText; +}; +} // namespace cru::platform::graph diff --git a/include/cru/platform/graph/Brush.hpp b/include/cru/platform/graph/Brush.hpp new file mode 100644 index 00000000..e67384de --- /dev/null +++ b/include/cru/platform/graph/Brush.hpp @@ -0,0 +1,11 @@ +#pragma once +#include "Resource.hpp" + +namespace cru::platform::graph { +struct IBrush : virtual IGraphResource {}; + +struct ISolidColorBrush : virtual IBrush { + virtual Color GetColor() = 0; + virtual void SetColor(const Color& color) = 0; +}; +} // namespace cru::platform::graph diff --git a/include/cru/platform/graph/Factory.hpp b/include/cru/platform/graph/Factory.hpp new file mode 100644 index 00000000..0a425d15 --- /dev/null +++ b/include/cru/platform/graph/Factory.hpp @@ -0,0 +1,25 @@ +#pragma once +#include "Resource.hpp" + +#include "Brush.hpp" +#include "Font.hpp" +#include "Geometry.hpp" +#include "TextLayout.hpp" + +#include +#include + +namespace cru::platform::graph { +// Entry point of the graph module. +struct IGraphFactory : virtual INativeResource { + virtual std::unique_ptr CreateSolidColorBrush() = 0; + + virtual std::unique_ptr CreateGeometryBuilder() = 0; + + virtual std::unique_ptr CreateFont(const std::string_view& font_family, + float font_size) = 0; + + virtual std::unique_ptr CreateTextLayout( + std::shared_ptr font, std::string text) = 0; +}; +} // namespace cru::platform::graph diff --git a/include/cru/platform/graph/Font.hpp b/include/cru/platform/graph/Font.hpp new file mode 100644 index 00000000..182cc15b --- /dev/null +++ b/include/cru/platform/graph/Font.hpp @@ -0,0 +1,8 @@ +#pragma once +#include "Resource.hpp" + +namespace cru::platform::graph { +struct IFont : virtual IGraphResource { + virtual float GetFontSize() = 0; +}; +} // namespace cru::platform::graph diff --git a/include/cru/platform/graph/Geometry.hpp b/include/cru/platform/graph/Geometry.hpp new file mode 100644 index 00000000..354efd97 --- /dev/null +++ b/include/cru/platform/graph/Geometry.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "Resource.hpp" + +namespace cru::platform::graph { +struct IGeometry : virtual IGraphResource { + virtual bool FillContains(const Point& point) = 0; +}; + +// After called Build, calling every method will throw a + +struct IGeometryBuilder : virtual IGraphResource { + virtual void BeginFigure(const Point& point) = 0; + virtual void LineTo(const Point& point) = 0; + virtual void QuadraticBezierTo(const Point& control_point, + const Point& end_point) = 0; + virtual void CloseFigure(bool close) = 0; + + virtual std::unique_ptr Build() = 0; +}; +} // namespace cru::platform::graph diff --git a/include/cru/platform/graph/Painter.hpp b/include/cru/platform/graph/Painter.hpp new file mode 100644 index 00000000..27ae420b --- /dev/null +++ b/include/cru/platform/graph/Painter.hpp @@ -0,0 +1,29 @@ +#pragma once +#include "Resource.hpp" + +namespace cru::platform::graph { + +struct IPainter : virtual INativeResource { + virtual Matrix GetTransform() = 0; + virtual void SetTransform(const Matrix& matrix) = 0; + + virtual void Clear(const Color& color) = 0; + + virtual void StrokeRectangle(const Rect& rectangle, IBrush* brush, + float width) = 0; + virtual void FillRectangle(const Rect& rectangle, IBrush* brush) = 0; + + virtual void StrokeGeometry(IGeometry* geometry, IBrush* brush, + float width) = 0; + virtual void FillGeometry(IGeometry* geometry, IBrush* brush) = 0; + + virtual void DrawText(const Point& offset, ITextLayout* text_layout, + IBrush* brush) = 0; + + virtual void PushLayer(const Rect& bounds) = 0; + + virtual void PopLayer() = 0; + + virtual void EndDraw() = 0; +}; +} // namespace cru::platform::graph diff --git a/include/cru/platform/graph/Resource.hpp b/include/cru/platform/graph/Resource.hpp new file mode 100644 index 00000000..8859360c --- /dev/null +++ b/include/cru/platform/graph/Resource.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "Base.hpp" + +namespace cru::platform::graph { +struct IGraphFactory; + +struct IGraphResource : virtual INativeResource { + virtual IGraphFactory* GetGraphFactory() = 0; +}; +} // namespace cru::platform::graph diff --git a/include/cru/platform/graph/base.hpp b/include/cru/platform/graph/base.hpp deleted file mode 100644 index 61cfc5ef..00000000 --- a/include/cru/platform/graph/base.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once -#include "../GraphBase.hpp" -#include "../Matrix.hpp" -#include "../Resource.hpp" - -#include - -namespace cru::platform::graph { -// forward declarations -struct IGraphFactory; -struct IBrush; -struct ISolidColorBrush; -struct IFont; -struct IGeometry; -struct IGeometryBuilder; -struct IPainter; -struct ITextLayout; - -struct TextHitTestResult { - int position; - bool trailing; - bool insideText; -}; -} // namespace cru::platform::graph diff --git a/include/cru/platform/graph/brush.hpp b/include/cru/platform/graph/brush.hpp deleted file mode 100644 index e67384de..00000000 --- a/include/cru/platform/graph/brush.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#include "Resource.hpp" - -namespace cru::platform::graph { -struct IBrush : virtual IGraphResource {}; - -struct ISolidColorBrush : virtual IBrush { - virtual Color GetColor() = 0; - virtual void SetColor(const Color& color) = 0; -}; -} // namespace cru::platform::graph diff --git a/include/cru/platform/graph/factory.hpp b/include/cru/platform/graph/factory.hpp deleted file mode 100644 index 0a425d15..00000000 --- a/include/cru/platform/graph/factory.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once -#include "Resource.hpp" - -#include "Brush.hpp" -#include "Font.hpp" -#include "Geometry.hpp" -#include "TextLayout.hpp" - -#include -#include - -namespace cru::platform::graph { -// Entry point of the graph module. -struct IGraphFactory : virtual INativeResource { - virtual std::unique_ptr CreateSolidColorBrush() = 0; - - virtual std::unique_ptr CreateGeometryBuilder() = 0; - - virtual std::unique_ptr CreateFont(const std::string_view& font_family, - float font_size) = 0; - - virtual std::unique_ptr CreateTextLayout( - std::shared_ptr font, std::string text) = 0; -}; -} // namespace cru::platform::graph diff --git a/include/cru/platform/graph/font.hpp b/include/cru/platform/graph/font.hpp deleted file mode 100644 index 182cc15b..00000000 --- a/include/cru/platform/graph/font.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -#include "Resource.hpp" - -namespace cru::platform::graph { -struct IFont : virtual IGraphResource { - virtual float GetFontSize() = 0; -}; -} // namespace cru::platform::graph diff --git a/include/cru/platform/graph/geometry.hpp b/include/cru/platform/graph/geometry.hpp deleted file mode 100644 index 354efd97..00000000 --- a/include/cru/platform/graph/geometry.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once -#include "Resource.hpp" - -namespace cru::platform::graph { -struct IGeometry : virtual IGraphResource { - virtual bool FillContains(const Point& point) = 0; -}; - -// After called Build, calling every method will throw a - -struct IGeometryBuilder : virtual IGraphResource { - virtual void BeginFigure(const Point& point) = 0; - virtual void LineTo(const Point& point) = 0; - virtual void QuadraticBezierTo(const Point& control_point, - const Point& end_point) = 0; - virtual void CloseFigure(bool close) = 0; - - virtual std::unique_ptr Build() = 0; -}; -} // namespace cru::platform::graph diff --git a/include/cru/platform/graph/painter.hpp b/include/cru/platform/graph/painter.hpp deleted file mode 100644 index 27ae420b..00000000 --- a/include/cru/platform/graph/painter.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include "Resource.hpp" - -namespace cru::platform::graph { - -struct IPainter : virtual INativeResource { - virtual Matrix GetTransform() = 0; - virtual void SetTransform(const Matrix& matrix) = 0; - - virtual void Clear(const Color& color) = 0; - - virtual void StrokeRectangle(const Rect& rectangle, IBrush* brush, - float width) = 0; - virtual void FillRectangle(const Rect& rectangle, IBrush* brush) = 0; - - virtual void StrokeGeometry(IGeometry* geometry, IBrush* brush, - float width) = 0; - virtual void FillGeometry(IGeometry* geometry, IBrush* brush) = 0; - - virtual void DrawText(const Point& offset, ITextLayout* text_layout, - IBrush* brush) = 0; - - virtual void PushLayer(const Rect& bounds) = 0; - - virtual void PopLayer() = 0; - - virtual void EndDraw() = 0; -}; -} // namespace cru::platform::graph diff --git a/include/cru/platform/graph/resource.hpp b/include/cru/platform/graph/resource.hpp deleted file mode 100644 index 8859360c..00000000 --- a/include/cru/platform/graph/resource.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once -#include "Base.hpp" - -namespace cru::platform::graph { -struct IGraphFactory; - -struct IGraphResource : virtual INativeResource { - virtual IGraphFactory* GetGraphFactory() = 0; -}; -} // namespace cru::platform::graph diff --git a/include/cru/platform/graph/util/Painter.hpp b/include/cru/platform/graph/util/Painter.hpp new file mode 100644 index 00000000..f9aec027 --- /dev/null +++ b/include/cru/platform/graph/util/Painter.hpp @@ -0,0 +1,17 @@ +#pragma once +#include "../Painter.hpp" + +#include +#include + +namespace cru::platform::graph::util { +template +void WithTransform(IPainter* painter, const Matrix& matrix, const Fn& action) { + static_assert(std::is_invocable_v, + "Action must can be be invoked with painter."); + const auto old = painter->GetTransform(); + painter->SetTransform(old * matrix); + action(painter); + painter->SetTransform(old); +} +} // namespace cru::platform::graph::util diff --git a/include/cru/platform/graph/util/painter.hpp b/include/cru/platform/graph/util/painter.hpp deleted file mode 100644 index f9aec027..00000000 --- a/include/cru/platform/graph/util/painter.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once -#include "../Painter.hpp" - -#include -#include - -namespace cru::platform::graph::util { -template -void WithTransform(IPainter* painter, const Matrix& matrix, const Fn& action) { - static_assert(std::is_invocable_v, - "Action must can be be invoked with painter."); - const auto old = painter->GetTransform(); - painter->SetTransform(old * matrix); - action(painter); - painter->SetTransform(old); -} -} // namespace cru::platform::graph::util diff --git a/include/cru/platform/matrix.hpp b/include/cru/platform/matrix.hpp deleted file mode 100644 index cea5198b..00000000 --- a/include/cru/platform/matrix.hpp +++ /dev/null @@ -1,83 +0,0 @@ -#pragma once -#include "GraphBase.hpp" - -#include - -namespace cru::platform { -struct Matrix { - float m11; - float m12; - float m21; - float m22; - float m31; - float m32; - - Matrix() {} - - Matrix(float m11, float m12, float m21, float m22, float m31, float m32) { - this->m11 = m11; - this->m12 = m12; - this->m21 = m21; - this->m22 = m22; - this->m31 = m31; - this->m32 = m32; - } - - bool IsIdentity() const { - return m11 == 1.0f && m12 == 0.0f && m21 == 0.0f && m22 == 1.0f && - m31 == 0.0f && m32 == 0.0f; - } - - Matrix& operator*=(const Matrix& matrix) { - *this = Product(*this, matrix); - return *this; - } - - Matrix operator*(const Matrix& matrix) const { - return Product(*this, matrix); - } - - Point TransformPoint(const Point& point) const { - return Point{point.x * m11 + point.y * m21 + m31, - point.x * m12 + point.y * m22 + m32}; - } - - static Matrix Identity() { - return Matrix{1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f}; - } - - static Matrix Translation(float x, float y) { - return Matrix{1.0f, 0.0f, 0.0f, 1.0f, x, y}; - } - - static Matrix Scale(float sx, float sy) { - return Matrix{sx, 0.0f, 0.0f, sy, 0.0f, 0.0f}; - } - - static Matrix Rotation(float angle) { - float r = AngleToRadian(angle); - float s = std::sinf(r); - float c = std::cosf(r); - - return Matrix{c, s, -s, c, 0.0f, 0.0f}; - } - - static Matrix Skew(float sx, float sy) { - return Matrix{1.0f, sx, sy, 1.0f, 0.0f, 0.0f}; - } - - static Matrix Product(const Matrix& a, const Matrix& b) { - return Matrix{a.m11 * b.m11 + a.m12 * b.m21, - a.m11 * b.m12 + a.m12 * b.m22, - a.m21 * b.m11 + a.m22 * b.m21, - a.m21 * b.m12 + a.m22 * b.m22, - a.m31 * b.m11 + a.m32 * b.m21 + b.m31, - a.m31 * b.m12 + a.m32 * b.m22 + b.m32}; - } - - private: - static constexpr float PI = 3.1415926535f; - - static float AngleToRadian(float angle) { return angle / 180.f * PI; } -}; -} // namespace cru::platform diff --git a/include/cru/platform/native/Base.hpp b/include/cru/platform/native/Base.hpp new file mode 100644 index 00000000..bba7b960 --- /dev/null +++ b/include/cru/platform/native/Base.hpp @@ -0,0 +1,52 @@ +#pragma once +#include "cru/common/Base.hpp" +#include "cru/common/Bitmask.hpp" +#include "cru/platform/graph/Base.hpp" +#include "Keyboard.hpp" + +namespace cru::platform::native { +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 + +using MouseButton = Bitmask; + +namespace mouse_buttons { +constexpr MouseButton left{0b1}; +constexpr MouseButton middle{0b10}; +constexpr MouseButton right{0b100}; +} // namespace mouse_buttons + +enum class SystemCursorType { + Arrow, + Hand, +}; + +struct NativeMouseButtonEventArgs { + MouseButton button; + Point point; + KeyModifier modifier; +}; + +struct NativeKeyEventArgs { + KeyCode key; + KeyModifier modifier; +}; + +enum class FocusChangeType { Gain, Lost }; + +enum class MouseEnterLeaveType { Enter, Leave }; +} // namespace cru::platform::native diff --git a/include/cru/platform/native/Cursor.hpp b/include/cru/platform/native/Cursor.hpp new file mode 100644 index 00000000..6c8f8068 --- /dev/null +++ b/include/cru/platform/native/Cursor.hpp @@ -0,0 +1,15 @@ +#pragma once +#include "../Resource.hpp" +#include "Base.hpp" + +#include + +namespace cru::platform::native { +struct ICursor : virtual INativeResource {}; + +struct ICursorManager : virtual INativeResource { + virtual std::shared_ptr GetSystemCursor(SystemCursorType type) = 0; + + // TODO: Add method to create cursor. +}; +} // namespace cru::platform::native diff --git a/include/cru/platform/native/Keyboard.hpp b/include/cru/platform/native/Keyboard.hpp new file mode 100644 index 00000000..26a1409d --- /dev/null +++ b/include/cru/platform/native/Keyboard.hpp @@ -0,0 +1,120 @@ +#pragma once +#include "cru/common/Bitmask.hpp" + +namespace cru::platform::native { +// 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. +enum class KeyCode { + Unknown, + LeftButton, + MiddleButton, + RightButton, + Escape, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + N0, + N1, + N2, + N3, + N4, + N5, + N6, + N7, + N8, + N9, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + GraveAccent, + Tab, + CapsLock, + LeftShift, + LeftCtrl, + LeftSuper, + LeftAlt, + Minus, + Equal, + Backspace, + LeftSquareBracket, + RightSquareBracket, + BackSlash, + Semicolon, + Quote, + Comma, + Period, + Slash, + RightShift, + RightCtrl, + RightSuper, + RightAlt, + Insert, + Delete, + Home, + End, + PageUp, + PageDown, + Up, + Left, + Down, + Right, + PrintScreen, + ScrollLock, + Pause, + NumPad0, + NumPad1, + NumPad2, + NumPad3, + NumPad4, + NumPad5, + NumPad6, + NumPad7, + NumPad8, + NumPad9 +}; + +namespace details { +struct TagKeyModifier {}; +} // namespace details + +using KeyModifier = Bitmask; + +namespace key_modifiers { +constexpr KeyModifier shift{0b1}; +constexpr KeyModifier ctrl{0b10}; +constexpr KeyModifier alt{0b100}; +} // namespace key_modifiers +} // namespace cru::platform::native diff --git a/include/cru/platform/native/Window.hpp b/include/cru/platform/native/Window.hpp new file mode 100644 index 00000000..1fcac1fc --- /dev/null +++ b/include/cru/platform/native/Window.hpp @@ -0,0 +1,67 @@ +#pragma once +#include "../Resource.hpp" +#include "Base.hpp" +#include "cru/common/Event.hpp" + +#include + +namespace cru::platform::native { +// 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 GetResolver() = 0; + + virtual void Close() = 0; + + virtual INativeWindow* GetParent() = 0; + + virtual bool IsVisible() = 0; + virtual void SetVisible(bool is_visible) = 0; + + virtual Size GetClientSize() = 0; + virtual void SetClientSize(const Size& size) = 0; + + // Get the rect of the window containing frame. + // The lefttop of the rect is relative to screen lefttop. + virtual Rect GetWindowRect() = 0; + + // Set the rect of the window containing frame. + // The lefttop of the rect is relative to screen lefttop. + virtual void SetWindowRect(const Rect& rect) = 0; + + // Relative to client lefttop. + virtual Point GetMousePosition() = 0; + + virtual bool CaptureMouse() = 0; + virtual bool ReleaseMouse() = 0; + + virtual void SetCursor(std::shared_ptr cursor) = 0; + + virtual void RequestRepaint() = 0; + + // Remember to call EndDraw on return value and destroy it. + virtual std::unique_ptr BeginPaint() = 0; + + virtual IEvent* DestroyEvent() = 0; + virtual IEvent* PaintEvent() = 0; + virtual IEvent* ResizeEvent() = 0; + virtual IEvent* FocusEvent() = 0; + virtual IEvent* MouseEnterLeaveEvent() = 0; + virtual IEvent* MouseMoveEvent() = 0; + virtual IEvent* MouseDownEvent() = 0; + virtual IEvent* MouseUpEvent() = 0; + virtual IEvent* KeyDownEvent() = 0; + virtual IEvent* KeyUpEvent() = 0; +}; + +// See INativeWindow for more info. +struct INativeWindowResolver : virtual INativeResource { + // Think twice before you save the return value. + virtual INativeWindow* Resolve() = 0; +}; +} // namespace cru::platform::native diff --git a/include/cru/platform/native/base.hpp b/include/cru/platform/native/base.hpp deleted file mode 100644 index bba7b960..00000000 --- a/include/cru/platform/native/base.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once -#include "cru/common/Base.hpp" -#include "cru/common/Bitmask.hpp" -#include "cru/platform/graph/Base.hpp" -#include "Keyboard.hpp" - -namespace cru::platform::native { -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 - -using MouseButton = Bitmask; - -namespace mouse_buttons { -constexpr MouseButton left{0b1}; -constexpr MouseButton middle{0b10}; -constexpr MouseButton right{0b100}; -} // namespace mouse_buttons - -enum class SystemCursorType { - Arrow, - Hand, -}; - -struct NativeMouseButtonEventArgs { - MouseButton button; - Point point; - KeyModifier modifier; -}; - -struct NativeKeyEventArgs { - KeyCode key; - KeyModifier modifier; -}; - -enum class FocusChangeType { Gain, Lost }; - -enum class MouseEnterLeaveType { Enter, Leave }; -} // namespace cru::platform::native diff --git a/include/cru/platform/native/cursor.hpp b/include/cru/platform/native/cursor.hpp deleted file mode 100644 index 6c8f8068..00000000 --- a/include/cru/platform/native/cursor.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once -#include "../Resource.hpp" -#include "Base.hpp" - -#include - -namespace cru::platform::native { -struct ICursor : virtual INativeResource {}; - -struct ICursorManager : virtual INativeResource { - virtual std::shared_ptr GetSystemCursor(SystemCursorType type) = 0; - - // TODO: Add method to create cursor. -}; -} // namespace cru::platform::native diff --git a/include/cru/platform/native/keyboard.hpp b/include/cru/platform/native/keyboard.hpp deleted file mode 100644 index 26a1409d..00000000 --- a/include/cru/platform/native/keyboard.hpp +++ /dev/null @@ -1,120 +0,0 @@ -#pragma once -#include "cru/common/Bitmask.hpp" - -namespace cru::platform::native { -// 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. -enum class KeyCode { - Unknown, - LeftButton, - MiddleButton, - RightButton, - Escape, - F1, - F2, - F3, - F4, - F5, - F6, - F7, - F8, - F9, - F10, - F11, - F12, - N0, - N1, - N2, - N3, - N4, - N5, - N6, - N7, - N8, - N9, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - GraveAccent, - Tab, - CapsLock, - LeftShift, - LeftCtrl, - LeftSuper, - LeftAlt, - Minus, - Equal, - Backspace, - LeftSquareBracket, - RightSquareBracket, - BackSlash, - Semicolon, - Quote, - Comma, - Period, - Slash, - RightShift, - RightCtrl, - RightSuper, - RightAlt, - Insert, - Delete, - Home, - End, - PageUp, - PageDown, - Up, - Left, - Down, - Right, - PrintScreen, - ScrollLock, - Pause, - NumPad0, - NumPad1, - NumPad2, - NumPad3, - NumPad4, - NumPad5, - NumPad6, - NumPad7, - NumPad8, - NumPad9 -}; - -namespace details { -struct TagKeyModifier {}; -} // namespace details - -using KeyModifier = Bitmask; - -namespace key_modifiers { -constexpr KeyModifier shift{0b1}; -constexpr KeyModifier ctrl{0b10}; -constexpr KeyModifier alt{0b100}; -} // namespace key_modifiers -} // namespace cru::platform::native diff --git a/include/cru/platform/native/window.hpp b/include/cru/platform/native/window.hpp deleted file mode 100644 index 1fcac1fc..00000000 --- a/include/cru/platform/native/window.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once -#include "../Resource.hpp" -#include "Base.hpp" -#include "cru/common/Event.hpp" - -#include - -namespace cru::platform::native { -// 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 GetResolver() = 0; - - virtual void Close() = 0; - - virtual INativeWindow* GetParent() = 0; - - virtual bool IsVisible() = 0; - virtual void SetVisible(bool is_visible) = 0; - - virtual Size GetClientSize() = 0; - virtual void SetClientSize(const Size& size) = 0; - - // Get the rect of the window containing frame. - // The lefttop of the rect is relative to screen lefttop. - virtual Rect GetWindowRect() = 0; - - // Set the rect of the window containing frame. - // The lefttop of the rect is relative to screen lefttop. - virtual void SetWindowRect(const Rect& rect) = 0; - - // Relative to client lefttop. - virtual Point GetMousePosition() = 0; - - virtual bool CaptureMouse() = 0; - virtual bool ReleaseMouse() = 0; - - virtual void SetCursor(std::shared_ptr cursor) = 0; - - virtual void RequestRepaint() = 0; - - // Remember to call EndDraw on return value and destroy it. - virtual std::unique_ptr BeginPaint() = 0; - - virtual IEvent* DestroyEvent() = 0; - virtual IEvent* PaintEvent() = 0; - virtual IEvent* ResizeEvent() = 0; - virtual IEvent* FocusEvent() = 0; - virtual IEvent* MouseEnterLeaveEvent() = 0; - virtual IEvent* MouseMoveEvent() = 0; - virtual IEvent* MouseDownEvent() = 0; - virtual IEvent* MouseUpEvent() = 0; - virtual IEvent* KeyDownEvent() = 0; - virtual IEvent* KeyUpEvent() = 0; -}; - -// See INativeWindow for more info. -struct INativeWindowResolver : virtual INativeResource { - // Think twice before you save the return value. - virtual INativeWindow* Resolve() = 0; -}; -} // namespace cru::platform::native diff --git a/include/cru/platform/resource.hpp b/include/cru/platform/resource.hpp deleted file mode 100644 index 72cfaf52..00000000 --- a/include/cru/platform/resource.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once -#include "cru/common/Base.hpp" - -#include - -namespace cru::platform { -struct INativeResource : virtual Interface { - virtual std::string_view GetPlatformId() const = 0; -}; -} // namespace cru::platform diff --git a/include/cru/ui/Base.hpp b/include/cru/ui/Base.hpp new file mode 100644 index 00000000..b9ce0c88 --- /dev/null +++ b/include/cru/ui/Base.hpp @@ -0,0 +1,281 @@ +#pragma once +#include "cru/common/Base.hpp" +#include "cru/platform/graph/Base.hpp" +#include "cru/platform/native/Base.hpp" + +#include +#include +#include +#include + +namespace cru::ui { +//-------------------- region: import -------------------- +using cru::platform::Color; +using cru::platform::Ellipse; +using cru::platform::Matrix; +using cru::platform::Point; +using cru::platform::Rect; +using cru::platform::RoundedRect; +using cru::platform::Size; +using cru::platform::TextRange; +using cru::platform::Thickness; +using cru::platform::native::MouseButton; + +namespace mouse_buttons { +using cru::platform::native::mouse_buttons::left; +using cru::platform::native::mouse_buttons::middle; +using cru::platform::native::mouse_buttons::right; +} // namespace mouse_buttons + +namespace colors { +using cru::platform::colors::aliceblue; +using cru::platform::colors::antiquewhite; +using cru::platform::colors::aqua; +using cru::platform::colors::aquamarine; +using cru::platform::colors::azure; +using cru::platform::colors::beige; +using cru::platform::colors::bisque; +using cru::platform::colors::black; +using cru::platform::colors::blanchedalmond; +using cru::platform::colors::blue; +using cru::platform::colors::blueviolet; +using cru::platform::colors::brown; +using cru::platform::colors::burlywood; +using cru::platform::colors::cadetblue; +using cru::platform::colors::chartreuse; +using cru::platform::colors::chocolate; +using cru::platform::colors::coral; +using cru::platform::colors::cornflowerblue; +using cru::platform::colors::cornsilk; +using cru::platform::colors::crimson; +using cru::platform::colors::cyan; +using cru::platform::colors::darkblue; +using cru::platform::colors::darkcyan; +using cru::platform::colors::darkgoldenrod; +using cru::platform::colors::darkgray; +using cru::platform::colors::darkgreen; +using cru::platform::colors::darkgrey; +using cru::platform::colors::darkkhaki; +using cru::platform::colors::darkmagenta; +using cru::platform::colors::darkolivegreen; +using cru::platform::colors::darkorange; +using cru::platform::colors::darkorchid; +using cru::platform::colors::darkred; +using cru::platform::colors::darksalmon; +using cru::platform::colors::darkseagreen; +using cru::platform::colors::darkslateblue; +using cru::platform::colors::darkslategray; +using cru::platform::colors::darkslategrey; +using cru::platform::colors::darkturquoise; +using cru::platform::colors::darkviolet; +using cru::platform::colors::deeppink; +using cru::platform::colors::deepskyblue; +using cru::platform::colors::dimgray; +using cru::platform::colors::dimgrey; +using cru::platform::colors::dodgerblue; +using cru::platform::colors::firebrick; +using cru::platform::colors::floralwhite; +using cru::platform::colors::forestgreen; +using cru::platform::colors::fuchsia; +using cru::platform::colors::gainsboro; +using cru::platform::colors::ghostwhite; +using cru::platform::colors::gold; +using cru::platform::colors::goldenrod; +using cru::platform::colors::gray; +using cru::platform::colors::green; +using cru::platform::colors::greenyellow; +using cru::platform::colors::grey; +using cru::platform::colors::honeydew; +using cru::platform::colors::hotpink; +using cru::platform::colors::indianred; +using cru::platform::colors::indigo; +using cru::platform::colors::ivory; +using cru::platform::colors::khaki; +using cru::platform::colors::lavender; +using cru::platform::colors::lavenderblush; +using cru::platform::colors::lawngreen; +using cru::platform::colors::lemonchiffon; +using cru::platform::colors::lightblue; +using cru::platform::colors::lightcoral; +using cru::platform::colors::lightcyan; +using cru::platform::colors::lightgoldenrodyellow; +using cru::platform::colors::lightgray; +using cru::platform::colors::lightgreen; +using cru::platform::colors::lightgrey; +using cru::platform::colors::lightpink; +using cru::platform::colors::lightsalmon; +using cru::platform::colors::lightseagreen; +using cru::platform::colors::lightskyblue; +using cru::platform::colors::lightslategray; +using cru::platform::colors::lightslategrey; +using cru::platform::colors::lightsteelblue; +using cru::platform::colors::lightyellow; +using cru::platform::colors::lime; +using cru::platform::colors::limegreen; +using cru::platform::colors::linen; +using cru::platform::colors::magenta; +using cru::platform::colors::maroon; +using cru::platform::colors::mediumaquamarine; +using cru::platform::colors::mediumblue; +using cru::platform::colors::mediumorchid; +using cru::platform::colors::mediumpurple; +using cru::platform::colors::mediumseagreen; +using cru::platform::colors::mediumslateblue; +using cru::platform::colors::mediumspringgreen; +using cru::platform::colors::mediumturquoise; +using cru::platform::colors::mediumvioletred; +using cru::platform::colors::midnightblue; +using cru::platform::colors::mintcream; +using cru::platform::colors::mistyrose; +using cru::platform::colors::moccasin; +using cru::platform::colors::navajowhite; +using cru::platform::colors::navy; +using cru::platform::colors::oldlace; +using cru::platform::colors::olive; +using cru::platform::colors::olivedrab; +using cru::platform::colors::orange; +using cru::platform::colors::orangered; +using cru::platform::colors::orchid; +using cru::platform::colors::palegoldenrod; +using cru::platform::colors::palegreen; +using cru::platform::colors::paleturquoise; +using cru::platform::colors::palevioletred; +using cru::platform::colors::papayawhip; +using cru::platform::colors::peachpuff; +using cru::platform::colors::peru; +using cru::platform::colors::pink; +using cru::platform::colors::plum; +using cru::platform::colors::powderblue; +using cru::platform::colors::purple; +using cru::platform::colors::rebeccapurple; +using cru::platform::colors::red; +using cru::platform::colors::rosybrown; +using cru::platform::colors::royalblue; +using cru::platform::colors::saddlebrown; +using cru::platform::colors::salmon; +using cru::platform::colors::sandybrown; +using cru::platform::colors::seagreen; +using cru::platform::colors::seashell; +using cru::platform::colors::sienna; +using cru::platform::colors::silver; +using cru::platform::colors::skyblue; +using cru::platform::colors::slateblue; +using cru::platform::colors::slategray; +using cru::platform::colors::slategrey; +using cru::platform::colors::snow; +using cru::platform::colors::springgreen; +using cru::platform::colors::steelblue; +using cru::platform::colors::tan; +using cru::platform::colors::teal; +using cru::platform::colors::thistle; +using cru::platform::colors::tomato; +using cru::platform::colors::transparent; +using cru::platform::colors::turquoise; +using cru::platform::colors::violet; +using cru::platform::colors::wheat; +using cru::platform::colors::white; +using cru::platform::colors::whitesmoke; +using cru::platform::colors::yellow; +using cru::platform::colors::yellowgreen; +} // namespace colors + +//-------------------- region: forward declaration -------------------- +class Window; +class Control; +class ClickDetector; +class UiHost; + +//-------------------- region: basic types -------------------- +namespace internal { +constexpr int align_start = 0; +constexpr int align_end = align_start + 1; +constexpr int align_center = align_end + 1; +} // namespace internal + +enum class Alignment { + Start = internal::align_start, + End = internal::align_end, + Center = internal::align_center, +}; + +struct CornerRadius { + constexpr CornerRadius() + : left_top(), right_top(), left_bottom(), right_bottom() {} + constexpr CornerRadius(const float& value) + : CornerRadius(Point{value, value}) {} + constexpr CornerRadius(const Point& value) + : left_top(value), + right_top(value), + left_bottom(value), + right_bottom(value) {} + constexpr CornerRadius(Point left_top, Point right_top, Point left_bottom, + Point right_bottom) + : left_top(left_top), + right_top(right_top), + left_bottom(left_bottom), + right_bottom(right_bottom) {} + + Point left_top; + Point right_top; + Point left_bottom; + Point right_bottom; +}; + +inline bool operator==(const CornerRadius& left, const CornerRadius& right) { + return left.left_top == right.left_top && + left.left_bottom == right.left_bottom && + left.right_top == right.right_top && + left.right_bottom == right.right_bottom; +} + +inline bool operator!=(const CornerRadius& left, const CornerRadius& right) { + return !(left == right); +} + +struct BorderStyle { + std::shared_ptr border_brush; + Thickness border_thickness; + CornerRadius border_radius; + std::shared_ptr foreground_brush; + std::shared_ptr background_brush; +}; + +class CanvasPaintEventArgs { + public: + CanvasPaintEventArgs(platform::graph::IPainter* painter, + const Rect& paint_rect) + : painter_(painter), paint_rect_(paint_rect) {} + CRU_DEFAULT_COPY(CanvasPaintEventArgs) + CRU_DEFAULT_MOVE(CanvasPaintEventArgs) + ~CanvasPaintEventArgs() = default; + + platform::graph::IPainter* GetPainter() const { return painter_; } + Rect GetPaintRect() const { return paint_rect_; } + + private: + platform::graph::IPainter* painter_; + Rect paint_rect_; +}; + +enum class FlexDirection { + Horizontal, + HorizontalReverse, + Vertical, + VertivalReverse +}; + +using FlexMainAlignment = Alignment; +using FlexCrossAlignment = Alignment; + +struct FlexChildLayoutData { + float expand_factor = 0; + float shrink_factor = 1; + // nullopt stands for looking at parent's setting + std::optional cross_alignment = std::nullopt; +}; + +struct StackChildLayoutData { + Alignment horizontal = Alignment::Start; + Alignment vertical = Alignment::Start; +}; +} // namespace cru::ui diff --git a/include/cru/ui/Control.hpp b/include/cru/ui/Control.hpp new file mode 100644 index 00000000..347163be --- /dev/null +++ b/include/cru/ui/Control.hpp @@ -0,0 +1,152 @@ +#pragma once +#include "Base.hpp" + +#include "cru/common/Event.hpp" +#include "render/Base.hpp" +#include "UiEvent.hpp" + +#include + +namespace cru::ui { +class Control : public Object { + friend UiHost; + + protected: + Control(); + + public: + Control(const Control& other) = delete; + Control(Control&& other) = delete; + Control& operator=(const Control& other) = delete; + Control& operator=(Control&& other) = delete; + ~Control() override = default; + + public: + virtual std::string_view GetControlType() const = 0; + + //*************** region: tree *************** + public: + // Get the ui host if attached, otherwise, return nullptr. + UiHost* GetUiHost() const { return ui_host_; } + + Control* GetParent() const { return parent_; } + + virtual const std::vector& GetChildren() const = 0; + + // Traverse the tree rooted the control including itself. + void TraverseDescendants(const std::function& predicate); + + void _SetParent(Control* parent); + void _SetDescendantUiHost(UiHost* host); + + private: + static void _TraverseDescendants( + Control* control, const std::function& predicate); + + public: + virtual render::RenderObject* GetRenderObject() const = 0; + + //*************** region: focus *************** + public: + bool RequestFocus(); + + bool HasFocus(); + + //*************** region: mouse *************** + public: + bool IsMouseOver() const { return is_mouse_over_; } + + bool CaptureMouse(); + + bool ReleaseMouse(); + + bool IsMouseCaptured(); + + //*************** region: cursor *************** + // Cursor is inherited from parent recursively if not set. + public: + // null for not set + std::shared_ptr GetCursor(); + + // will not return nullptr + std::shared_ptr GetInheritedCursor(); + + // null to unset + void SetCursor(std::shared_ptr cursor); + + //*************** region: events *************** + public: + // Raised when mouse enter the control. Even when the control itself captures + // the mouse, this event is raised as regular. But if mouse is captured by + // another control, the control will not receive any mouse enter event. You + // can use `IsMouseCaptured` to get more info. + event::RoutedEvent* MouseEnterEvent() { + return &mouse_enter_event_; + } + // Raised when mouse is leave the control. Even when the control itself + // captures the mouse, this event is raised as regular. But if mouse is + // captured by another control, the control will not receive any mouse leave + // event. You can use `IsMouseCaptured` to get more info. + event::RoutedEvent* MouseLeaveEvent() { + return &mouse_leave_event_; + } + // Raised when mouse is move in the control. + event::RoutedEvent* MouseMoveEvent() { + return &mouse_move_event_; + } + // Raised when a mouse button is pressed in the control. + event::RoutedEvent* MouseDownEvent() { + return &mouse_down_event_; + } + // Raised when a mouse button is released in the control. + event::RoutedEvent* MouseUpEvent() { + return &mouse_up_event_; + } + event::RoutedEvent* MouseWheelEvent() { + return &mouse_wheel_event_; + } + event::RoutedEvent* KeyDownEvent() { + return &key_down_event_; + } + event::RoutedEvent* KeyUpEvent() { + return &key_up_event_; + } + event::RoutedEvent* GainFocusEvent() { + return &gain_focus_event_; + } + event::RoutedEvent* LoseFocusEvent() { + return &lose_focus_event_; + } + + private: + event::RoutedEvent mouse_enter_event_; + event::RoutedEvent mouse_leave_event_; + event::RoutedEvent mouse_move_event_; + event::RoutedEvent mouse_down_event_; + event::RoutedEvent mouse_up_event_; + event::RoutedEvent mouse_wheel_event_; + + event::RoutedEvent key_down_event_; + event::RoutedEvent key_up_event_; + + event::RoutedEvent gain_focus_event_; + event::RoutedEvent lose_focus_event_; + + //*************** region: tree *************** + protected: + virtual void OnParentChanged(Control* old_parent, Control* new_parent); + virtual void OnAttachToHost(UiHost* host); + virtual void OnDetachFromHost(UiHost* host); + + virtual void OnMouseHoverChange(bool newHover) { CRU_UNUSED(newHover) } + + private: + UiHost* ui_host_ = nullptr; + Control* parent_ = nullptr; + + private: + bool is_mouse_over_ = false; + + std::shared_ptr cursor_ = nullptr; +}; +} // namespace cru::ui diff --git a/include/cru/ui/Window.hpp b/include/cru/ui/Window.hpp new file mode 100644 index 00000000..eb2ecfbb --- /dev/null +++ b/include/cru/ui/Window.hpp @@ -0,0 +1,40 @@ +#pragma once +#include "ContentControl.hpp" + +namespace cru::ui { +class Window final : public ContentControl { + friend UiHost; + + public: + static constexpr std::string_view control_type = "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::string_view GetControlType() const final; + + render::RenderObject* GetRenderObject() const override; + + protected: + void OnChildChanged(Control* old_child, Control* new_child) override; + + private: + std::unique_ptr 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/base.hpp b/include/cru/ui/base.hpp deleted file mode 100644 index b9ce0c88..00000000 --- a/include/cru/ui/base.hpp +++ /dev/null @@ -1,281 +0,0 @@ -#pragma once -#include "cru/common/Base.hpp" -#include "cru/platform/graph/Base.hpp" -#include "cru/platform/native/Base.hpp" - -#include -#include -#include -#include - -namespace cru::ui { -//-------------------- region: import -------------------- -using cru::platform::Color; -using cru::platform::Ellipse; -using cru::platform::Matrix; -using cru::platform::Point; -using cru::platform::Rect; -using cru::platform::RoundedRect; -using cru::platform::Size; -using cru::platform::TextRange; -using cru::platform::Thickness; -using cru::platform::native::MouseButton; - -namespace mouse_buttons { -using cru::platform::native::mouse_buttons::left; -using cru::platform::native::mouse_buttons::middle; -using cru::platform::native::mouse_buttons::right; -} // namespace mouse_buttons - -namespace colors { -using cru::platform::colors::aliceblue; -using cru::platform::colors::antiquewhite; -using cru::platform::colors::aqua; -using cru::platform::colors::aquamarine; -using cru::platform::colors::azure; -using cru::platform::colors::beige; -using cru::platform::colors::bisque; -using cru::platform::colors::black; -using cru::platform::colors::blanchedalmond; -using cru::platform::colors::blue; -using cru::platform::colors::blueviolet; -using cru::platform::colors::brown; -using cru::platform::colors::burlywood; -using cru::platform::colors::cadetblue; -using cru::platform::colors::chartreuse; -using cru::platform::colors::chocolate; -using cru::platform::colors::coral; -using cru::platform::colors::cornflowerblue; -using cru::platform::colors::cornsilk; -using cru::platform::colors::crimson; -using cru::platform::colors::cyan; -using cru::platform::colors::darkblue; -using cru::platform::colors::darkcyan; -using cru::platform::colors::darkgoldenrod; -using cru::platform::colors::darkgray; -using cru::platform::colors::darkgreen; -using cru::platform::colors::darkgrey; -using cru::platform::colors::darkkhaki; -using cru::platform::colors::darkmagenta; -using cru::platform::colors::darkolivegreen; -using cru::platform::colors::darkorange; -using cru::platform::colors::darkorchid; -using cru::platform::colors::darkred; -using cru::platform::colors::darksalmon; -using cru::platform::colors::darkseagreen; -using cru::platform::colors::darkslateblue; -using cru::platform::colors::darkslategray; -using cru::platform::colors::darkslategrey; -using cru::platform::colors::darkturquoise; -using cru::platform::colors::darkviolet; -using cru::platform::colors::deeppink; -using cru::platform::colors::deepskyblue; -using cru::platform::colors::dimgray; -using cru::platform::colors::dimgrey; -using cru::platform::colors::dodgerblue; -using cru::platform::colors::firebrick; -using cru::platform::colors::floralwhite; -using cru::platform::colors::forestgreen; -using cru::platform::colors::fuchsia; -using cru::platform::colors::gainsboro; -using cru::platform::colors::ghostwhite; -using cru::platform::colors::gold; -using cru::platform::colors::goldenrod; -using cru::platform::colors::gray; -using cru::platform::colors::green; -using cru::platform::colors::greenyellow; -using cru::platform::colors::grey; -using cru::platform::colors::honeydew; -using cru::platform::colors::hotpink; -using cru::platform::colors::indianred; -using cru::platform::colors::indigo; -using cru::platform::colors::ivory; -using cru::platform::colors::khaki; -using cru::platform::colors::lavender; -using cru::platform::colors::lavenderblush; -using cru::platform::colors::lawngreen; -using cru::platform::colors::lemonchiffon; -using cru::platform::colors::lightblue; -using cru::platform::colors::lightcoral; -using cru::platform::colors::lightcyan; -using cru::platform::colors::lightgoldenrodyellow; -using cru::platform::colors::lightgray; -using cru::platform::colors::lightgreen; -using cru::platform::colors::lightgrey; -using cru::platform::colors::lightpink; -using cru::platform::colors::lightsalmon; -using cru::platform::colors::lightseagreen; -using cru::platform::colors::lightskyblue; -using cru::platform::colors::lightslategray; -using cru::platform::colors::lightslategrey; -using cru::platform::colors::lightsteelblue; -using cru::platform::colors::lightyellow; -using cru::platform::colors::lime; -using cru::platform::colors::limegreen; -using cru::platform::colors::linen; -using cru::platform::colors::magenta; -using cru::platform::colors::maroon; -using cru::platform::colors::mediumaquamarine; -using cru::platform::colors::mediumblue; -using cru::platform::colors::mediumorchid; -using cru::platform::colors::mediumpurple; -using cru::platform::colors::mediumseagreen; -using cru::platform::colors::mediumslateblue; -using cru::platform::colors::mediumspringgreen; -using cru::platform::colors::mediumturquoise; -using cru::platform::colors::mediumvioletred; -using cru::platform::colors::midnightblue; -using cru::platform::colors::mintcream; -using cru::platform::colors::mistyrose; -using cru::platform::colors::moccasin; -using cru::platform::colors::navajowhite; -using cru::platform::colors::navy; -using cru::platform::colors::oldlace; -using cru::platform::colors::olive; -using cru::platform::colors::olivedrab; -using cru::platform::colors::orange; -using cru::platform::colors::orangered; -using cru::platform::colors::orchid; -using cru::platform::colors::palegoldenrod; -using cru::platform::colors::palegreen; -using cru::platform::colors::paleturquoise; -using cru::platform::colors::palevioletred; -using cru::platform::colors::papayawhip; -using cru::platform::colors::peachpuff; -using cru::platform::colors::peru; -using cru::platform::colors::pink; -using cru::platform::colors::plum; -using cru::platform::colors::powderblue; -using cru::platform::colors::purple; -using cru::platform::colors::rebeccapurple; -using cru::platform::colors::red; -using cru::platform::colors::rosybrown; -using cru::platform::colors::royalblue; -using cru::platform::colors::saddlebrown; -using cru::platform::colors::salmon; -using cru::platform::colors::sandybrown; -using cru::platform::colors::seagreen; -using cru::platform::colors::seashell; -using cru::platform::colors::sienna; -using cru::platform::colors::silver; -using cru::platform::colors::skyblue; -using cru::platform::colors::slateblue; -using cru::platform::colors::slategray; -using cru::platform::colors::slategrey; -using cru::platform::colors::snow; -using cru::platform::colors::springgreen; -using cru::platform::colors::steelblue; -using cru::platform::colors::tan; -using cru::platform::colors::teal; -using cru::platform::colors::thistle; -using cru::platform::colors::tomato; -using cru::platform::colors::transparent; -using cru::platform::colors::turquoise; -using cru::platform::colors::violet; -using cru::platform::colors::wheat; -using cru::platform::colors::white; -using cru::platform::colors::whitesmoke; -using cru::platform::colors::yellow; -using cru::platform::colors::yellowgreen; -} // namespace colors - -//-------------------- region: forward declaration -------------------- -class Window; -class Control; -class ClickDetector; -class UiHost; - -//-------------------- region: basic types -------------------- -namespace internal { -constexpr int align_start = 0; -constexpr int align_end = align_start + 1; -constexpr int align_center = align_end + 1; -} // namespace internal - -enum class Alignment { - Start = internal::align_start, - End = internal::align_end, - Center = internal::align_center, -}; - -struct CornerRadius { - constexpr CornerRadius() - : left_top(), right_top(), left_bottom(), right_bottom() {} - constexpr CornerRadius(const float& value) - : CornerRadius(Point{value, value}) {} - constexpr CornerRadius(const Point& value) - : left_top(value), - right_top(value), - left_bottom(value), - right_bottom(value) {} - constexpr CornerRadius(Point left_top, Point right_top, Point left_bottom, - Point right_bottom) - : left_top(left_top), - right_top(right_top), - left_bottom(left_bottom), - right_bottom(right_bottom) {} - - Point left_top; - Point right_top; - Point left_bottom; - Point right_bottom; -}; - -inline bool operator==(const CornerRadius& left, const CornerRadius& right) { - return left.left_top == right.left_top && - left.left_bottom == right.left_bottom && - left.right_top == right.right_top && - left.right_bottom == right.right_bottom; -} - -inline bool operator!=(const CornerRadius& left, const CornerRadius& right) { - return !(left == right); -} - -struct BorderStyle { - std::shared_ptr border_brush; - Thickness border_thickness; - CornerRadius border_radius; - std::shared_ptr foreground_brush; - std::shared_ptr background_brush; -}; - -class CanvasPaintEventArgs { - public: - CanvasPaintEventArgs(platform::graph::IPainter* painter, - const Rect& paint_rect) - : painter_(painter), paint_rect_(paint_rect) {} - CRU_DEFAULT_COPY(CanvasPaintEventArgs) - CRU_DEFAULT_MOVE(CanvasPaintEventArgs) - ~CanvasPaintEventArgs() = default; - - platform::graph::IPainter* GetPainter() const { return painter_; } - Rect GetPaintRect() const { return paint_rect_; } - - private: - platform::graph::IPainter* painter_; - Rect paint_rect_; -}; - -enum class FlexDirection { - Horizontal, - HorizontalReverse, - Vertical, - VertivalReverse -}; - -using FlexMainAlignment = Alignment; -using FlexCrossAlignment = Alignment; - -struct FlexChildLayoutData { - float expand_factor = 0; - float shrink_factor = 1; - // nullopt stands for looking at parent's setting - std::optional cross_alignment = std::nullopt; -}; - -struct StackChildLayoutData { - Alignment horizontal = Alignment::Start; - Alignment vertical = Alignment::Start; -}; -} // namespace cru::ui diff --git a/include/cru/ui/control.hpp b/include/cru/ui/control.hpp deleted file mode 100644 index 347163be..00000000 --- a/include/cru/ui/control.hpp +++ /dev/null @@ -1,152 +0,0 @@ -#pragma once -#include "Base.hpp" - -#include "cru/common/Event.hpp" -#include "render/Base.hpp" -#include "UiEvent.hpp" - -#include - -namespace cru::ui { -class Control : public Object { - friend UiHost; - - protected: - Control(); - - public: - Control(const Control& other) = delete; - Control(Control&& other) = delete; - Control& operator=(const Control& other) = delete; - Control& operator=(Control&& other) = delete; - ~Control() override = default; - - public: - virtual std::string_view GetControlType() const = 0; - - //*************** region: tree *************** - public: - // Get the ui host if attached, otherwise, return nullptr. - UiHost* GetUiHost() const { return ui_host_; } - - Control* GetParent() const { return parent_; } - - virtual const std::vector& GetChildren() const = 0; - - // Traverse the tree rooted the control including itself. - void TraverseDescendants(const std::function& predicate); - - void _SetParent(Control* parent); - void _SetDescendantUiHost(UiHost* host); - - private: - static void _TraverseDescendants( - Control* control, const std::function& predicate); - - public: - virtual render::RenderObject* GetRenderObject() const = 0; - - //*************** region: focus *************** - public: - bool RequestFocus(); - - bool HasFocus(); - - //*************** region: mouse *************** - public: - bool IsMouseOver() const { return is_mouse_over_; } - - bool CaptureMouse(); - - bool ReleaseMouse(); - - bool IsMouseCaptured(); - - //*************** region: cursor *************** - // Cursor is inherited from parent recursively if not set. - public: - // null for not set - std::shared_ptr GetCursor(); - - // will not return nullptr - std::shared_ptr GetInheritedCursor(); - - // null to unset - void SetCursor(std::shared_ptr cursor); - - //*************** region: events *************** - public: - // Raised when mouse enter the control. Even when the control itself captures - // the mouse, this event is raised as regular. But if mouse is captured by - // another control, the control will not receive any mouse enter event. You - // can use `IsMouseCaptured` to get more info. - event::RoutedEvent* MouseEnterEvent() { - return &mouse_enter_event_; - } - // Raised when mouse is leave the control. Even when the control itself - // captures the mouse, this event is raised as regular. But if mouse is - // captured by another control, the control will not receive any mouse leave - // event. You can use `IsMouseCaptured` to get more info. - event::RoutedEvent* MouseLeaveEvent() { - return &mouse_leave_event_; - } - // Raised when mouse is move in the control. - event::RoutedEvent* MouseMoveEvent() { - return &mouse_move_event_; - } - // Raised when a mouse button is pressed in the control. - event::RoutedEvent* MouseDownEvent() { - return &mouse_down_event_; - } - // Raised when a mouse button is released in the control. - event::RoutedEvent* MouseUpEvent() { - return &mouse_up_event_; - } - event::RoutedEvent* MouseWheelEvent() { - return &mouse_wheel_event_; - } - event::RoutedEvent* KeyDownEvent() { - return &key_down_event_; - } - event::RoutedEvent* KeyUpEvent() { - return &key_up_event_; - } - event::RoutedEvent* GainFocusEvent() { - return &gain_focus_event_; - } - event::RoutedEvent* LoseFocusEvent() { - return &lose_focus_event_; - } - - private: - event::RoutedEvent mouse_enter_event_; - event::RoutedEvent mouse_leave_event_; - event::RoutedEvent mouse_move_event_; - event::RoutedEvent mouse_down_event_; - event::RoutedEvent mouse_up_event_; - event::RoutedEvent mouse_wheel_event_; - - event::RoutedEvent key_down_event_; - event::RoutedEvent key_up_event_; - - event::RoutedEvent gain_focus_event_; - event::RoutedEvent lose_focus_event_; - - //*************** region: tree *************** - protected: - virtual void OnParentChanged(Control* old_parent, Control* new_parent); - virtual void OnAttachToHost(UiHost* host); - virtual void OnDetachFromHost(UiHost* host); - - virtual void OnMouseHoverChange(bool newHover) { CRU_UNUSED(newHover) } - - private: - UiHost* ui_host_ = nullptr; - Control* parent_ = nullptr; - - private: - bool is_mouse_over_ = false; - - std::shared_ptr cursor_ = nullptr; -}; -} // namespace cru::ui diff --git a/include/cru/ui/controls/Base.hpp b/include/cru/ui/controls/Base.hpp new file mode 100644 index 00000000..b550601b --- /dev/null +++ b/include/cru/ui/controls/Base.hpp @@ -0,0 +1,24 @@ +#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 diff --git a/include/cru/ui/controls/Button.hpp b/include/cru/ui/controls/Button.hpp new file mode 100644 index 00000000..8a11409c --- /dev/null +++ b/include/cru/ui/controls/Button.hpp @@ -0,0 +1,42 @@ +#pragma once +#include "../ContentControl.hpp" +#include "Base.hpp" + +#include "../ClickDetector.hpp" + +namespace cru::ui::controls { +class Button : public ContentControl { + public: + static constexpr std::string_view control_type = "Button"; + + static Button* Create() { return new Button(); } + + protected: + Button(); + + public: + Button(const Button& other) = delete; + Button(Button&& other) = delete; + Button& operator=(const Button& other) = delete; + Button& operator=(Button&& other) = delete; + ~Button() override; + + std::string_view GetControlType() const final { return control_type; } + + render::RenderObject* GetRenderObject() const override; + + public: + const ButtonStyle& GetStyle() const { return style_; } + void SetStyle(ButtonStyle style); + + protected: + void OnChildChanged(Control* old_child, Control* new_child) override; + + private: + std::unique_ptr render_object_{}; + + ButtonStyle style_; + + ClickDetector click_detector_; +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/Container.hpp b/include/cru/ui/controls/Container.hpp new file mode 100644 index 00000000..e3d78365 --- /dev/null +++ b/include/cru/ui/controls/Container.hpp @@ -0,0 +1,28 @@ +#pragma once +#include "../ContentControl.hpp" + +namespace cru::ui::controls { +class Container : public ContentControl { + static constexpr std::string_view control_type = "Container"; + + protected: + Container(); + + public: + CRU_DELETE_COPY(Container) + CRU_DELETE_MOVE(Container) + + ~Container() override; + + public: + std::string_view GetControlType() const final { return control_type; } + + render::RenderObject* GetRenderObject() const override; + + protected: + void OnChildChanged(Control* old_child, Control* new_child) override; + + private: + std::unique_ptr render_object_; +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/base.hpp b/include/cru/ui/controls/base.hpp deleted file mode 100644 index b550601b..00000000 --- a/include/cru/ui/controls/base.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#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 diff --git a/include/cru/ui/controls/button.hpp b/include/cru/ui/controls/button.hpp deleted file mode 100644 index 8a11409c..00000000 --- a/include/cru/ui/controls/button.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once -#include "../ContentControl.hpp" -#include "Base.hpp" - -#include "../ClickDetector.hpp" - -namespace cru::ui::controls { -class Button : public ContentControl { - public: - static constexpr std::string_view control_type = "Button"; - - static Button* Create() { return new Button(); } - - protected: - Button(); - - public: - Button(const Button& other) = delete; - Button(Button&& other) = delete; - Button& operator=(const Button& other) = delete; - Button& operator=(Button&& other) = delete; - ~Button() override; - - std::string_view GetControlType() const final { return control_type; } - - render::RenderObject* GetRenderObject() const override; - - public: - const ButtonStyle& GetStyle() const { return style_; } - void SetStyle(ButtonStyle style); - - protected: - void OnChildChanged(Control* old_child, Control* new_child) override; - - private: - std::unique_ptr render_object_{}; - - ButtonStyle style_; - - ClickDetector click_detector_; -}; -} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/container.hpp b/include/cru/ui/controls/container.hpp deleted file mode 100644 index e3d78365..00000000 --- a/include/cru/ui/controls/container.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include "../ContentControl.hpp" - -namespace cru::ui::controls { -class Container : public ContentControl { - static constexpr std::string_view control_type = "Container"; - - protected: - Container(); - - public: - CRU_DELETE_COPY(Container) - CRU_DELETE_MOVE(Container) - - ~Container() override; - - public: - std::string_view GetControlType() const final { return control_type; } - - render::RenderObject* GetRenderObject() const override; - - protected: - void OnChildChanged(Control* old_child, Control* new_child) override; - - private: - std::unique_ptr render_object_; -}; -} // namespace cru::ui::controls diff --git a/include/cru/ui/render/Base.hpp b/include/cru/ui/render/Base.hpp new file mode 100644 index 00000000..c2af5e99 --- /dev/null +++ b/include/cru/ui/render/Base.hpp @@ -0,0 +1,12 @@ +#pragma once +#include "../Base.hpp" + +namespace cru::ui::render { +class RenderObject; +class BorderRenderObject; +class CanvasRenderObject; +class FlexLayoutRenderObject; +class StackLayoutRenderObject; +class TextRenderObject; +class WindowRenderObject; +} // namespace cru::ui::render diff --git a/include/cru/ui/render/base.hpp b/include/cru/ui/render/base.hpp deleted file mode 100644 index c2af5e99..00000000 --- a/include/cru/ui/render/base.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include "../Base.hpp" - -namespace cru::ui::render { -class RenderObject; -class BorderRenderObject; -class CanvasRenderObject; -class FlexLayoutRenderObject; -class StackLayoutRenderObject; -class TextRenderObject; -class WindowRenderObject; -} // namespace cru::ui::render diff --git a/include/cru/ui/window.hpp b/include/cru/ui/window.hpp deleted file mode 100644 index eb2ecfbb..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::string_view control_type = "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::string_view GetControlType() const final; - - render::RenderObject* GetRenderObject() const override; - - protected: - void OnChildChanged(Control* old_child, Control* new_child) override; - - private: - std::unique_ptr 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/win/Exception.hpp b/include/cru/win/Exception.hpp new file mode 100644 index 00000000..234aea69 --- /dev/null +++ b/include/cru/win/Exception.hpp @@ -0,0 +1,52 @@ +#pragma once +#include "WinPreConfig.hpp" + +#include "cru/platform/Exception.hpp" + +#include +#include + +namespace cru::platform::win { +class HResultError : public platform::PlatformException { + public: + explicit HResultError(HRESULT h_result); + explicit HResultError(HRESULT h_result, const std::string_view& message); + + CRU_DEFAULT_COPY(HResultError) + CRU_DEFAULT_MOVE(HResultError) + + ~HResultError() override = default; + + HRESULT GetHResult() const { return h_result_; } + + private: + HRESULT h_result_; +}; + +inline void ThrowIfFailed(const HRESULT h_result) { + if (FAILED(h_result)) throw HResultError(h_result); +} + +inline void ThrowIfFailed(const HRESULT h_result, + const std::string_view& message) { + if (FAILED(h_result)) throw HResultError(h_result, message); +} + +class Win32Error : public platform::PlatformException { + public: + // ::GetLastError is automatically called to get the error code. + // The same as Win32Error(::GetLastError(), message) + explicit Win32Error(const std::string_view& message); + Win32Error(DWORD error_code, const std::string_view& message); + + CRU_DEFAULT_COPY(Win32Error) + CRU_DEFAULT_MOVE(Win32Error) + + ~Win32Error() override = default; + + DWORD GetErrorCode() const { return error_code_; } + + private: + DWORD error_code_; +}; +} // namespace cru::platform::win diff --git a/include/cru/win/String.hpp b/include/cru/win/String.hpp new file mode 100644 index 00000000..3d68cff7 --- /dev/null +++ b/include/cru/win/String.hpp @@ -0,0 +1,107 @@ +/* +Because the text encoding problem on Windows, here I write some functions +related to text encoding. The utf-8 and utf-16 conversion function is provided +by win32 api. However win32 api does not provide any function about charactor +iteration or index by code point. (At least I haven't found.) I don't use icu +because it is not easy to build it on Windows and the bundled version in Windows +(https://docs.microsoft.com/en-us/windows/win32/intl/international-components-for-unicode--icu-) +is only available after Windows 10 Creators Update. + +Luckily, both utf-8 and utf-16 encoding are easy to learn and program with if we +only do simple iteration rather than do much sophisticated work about +complicated error situations. (And I learn the internal of the encoding by the +way.) +*/ + +#pragma once +#include "WinPreConfig.hpp" + +#include "cru/common/Base.hpp" + +#include +#include +#include +#include + +namespace cru::platform::win { +std::string ToUtf8String(const std::wstring_view& string); +std::wstring ToUtf16String(const std::string_view& string); + +inline bool IsSurrogatePair(wchar_t c) { return c >= 0xD800 && c <= 0xDFFF; } + +inline bool IsSurrogatePairLeading(wchar_t c) { + return c >= 0xD800 && c <= 0xDBFF; +} + +inline bool IsSurrogatePairTrailing(wchar_t c) { + return c >= 0xDC00 && c <= 0xDFFF; +} + +using CodePoint = std::int32_t; +constexpr CodePoint k_code_point_end = -1; + +class TextEncodeException : public std::runtime_error { + public: + using runtime_error::runtime_error; +}; + +class Utf8Iterator : public Object { + public: + Utf8Iterator(const std::string_view& string) : string_(string) {} + + CRU_DEFAULT_COPY(Utf8Iterator) + CRU_DEFAULT_MOVE(Utf8Iterator) + + ~Utf8Iterator() = default; + + public: + void SetToHead() { position_ = 0; } + + // Advance current position and get next code point. Return k_code_point_end + // if there is no next code unit(point). Throw TextEncodeException if decoding + // fails. + CodePoint Next(); + + int CurrentPosition() const { return this->position_; } + + private: + std::string_view string_; + int position_ = 0; +}; + +class Utf16Iterator : public Object { + static_assert( + sizeof(wchar_t) == 2, + "Emmm, according to my knowledge, wchar_t should be 2-length on " + "Windows. If not, Utf16 will be broken."); + + public: + Utf16Iterator(const std::wstring_view& string) : string_(string) {} + + CRU_DEFAULT_COPY(Utf16Iterator) + CRU_DEFAULT_MOVE(Utf16Iterator) + + ~Utf16Iterator() = default; + + public: + void SetToHead() { position_ = 0; } + + // Advance current position and get next code point. Return k_code_point_end + // if there is no next code unit(point). Throw TextEncodeException if decoding + // fails. + CodePoint Next(); + + int CurrentPosition() const { return this->position_; } + + private: + std::wstring_view string_; + int position_ = 0; +}; + +int IndexUtf8ToUtf16(const std::string_view& utf8_string, int utf8_index, + const std::wstring_view& utf16_string); + +int IndexUtf16ToUtf8(const std::wstring_view& utf16_string, int utf16_index, + const std::string_view& utf8_string); + +} // namespace cru::platform::win diff --git a/include/cru/win/exception.hpp b/include/cru/win/exception.hpp deleted file mode 100644 index 234aea69..00000000 --- a/include/cru/win/exception.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once -#include "WinPreConfig.hpp" - -#include "cru/platform/Exception.hpp" - -#include -#include - -namespace cru::platform::win { -class HResultError : public platform::PlatformException { - public: - explicit HResultError(HRESULT h_result); - explicit HResultError(HRESULT h_result, const std::string_view& message); - - CRU_DEFAULT_COPY(HResultError) - CRU_DEFAULT_MOVE(HResultError) - - ~HResultError() override = default; - - HRESULT GetHResult() const { return h_result_; } - - private: - HRESULT h_result_; -}; - -inline void ThrowIfFailed(const HRESULT h_result) { - if (FAILED(h_result)) throw HResultError(h_result); -} - -inline void ThrowIfFailed(const HRESULT h_result, - const std::string_view& message) { - if (FAILED(h_result)) throw HResultError(h_result, message); -} - -class Win32Error : public platform::PlatformException { - public: - // ::GetLastError is automatically called to get the error code. - // The same as Win32Error(::GetLastError(), message) - explicit Win32Error(const std::string_view& message); - Win32Error(DWORD error_code, const std::string_view& message); - - CRU_DEFAULT_COPY(Win32Error) - CRU_DEFAULT_MOVE(Win32Error) - - ~Win32Error() override = default; - - DWORD GetErrorCode() const { return error_code_; } - - private: - DWORD error_code_; -}; -} // namespace cru::platform::win diff --git a/include/cru/win/graph/direct/Brush.hpp b/include/cru/win/graph/direct/Brush.hpp new file mode 100644 index 00000000..df1debe3 --- /dev/null +++ b/include/cru/win/graph/direct/Brush.hpp @@ -0,0 +1,39 @@ +#pragma once +#include "ComResource.hpp" +#include "Resource.hpp" + +#include "cru/platform/graph/Brush.hpp" + +namespace cru::platform::graph::win::direct { +struct ID2DBrush : virtual IBrush { + virtual ID2D1Brush* GetD2DBrushInterface() const = 0; +}; + +class D2DSolidColorBrush : public DirectGraphResource, + public virtual ISolidColorBrush, + public virtual ID2DBrush, + public virtual IComResource { + public: + explicit D2DSolidColorBrush(DirectGraphFactory* factory); + + CRU_DELETE_COPY(D2DSolidColorBrush) + CRU_DELETE_MOVE(D2DSolidColorBrush) + + ~D2DSolidColorBrush() override = default; + + public: + Color GetColor() override { return color_; } + void SetColor(const Color& color) override; + + ID2D1Brush* GetD2DBrushInterface() const override { return brush_.Get(); } + + ID2D1SolidColorBrush* GetComInterface() const override { + return brush_.Get(); + } + + private: + Color color_ = colors::black; + + Microsoft::WRL::ComPtr brush_; +}; +} // namespace cru::platform::graph::win::direct diff --git a/include/cru/win/graph/direct/Exception.hpp b/include/cru/win/graph/direct/Exception.hpp new file mode 100644 index 00000000..8b62e8fa --- /dev/null +++ b/include/cru/win/graph/direct/Exception.hpp @@ -0,0 +1,7 @@ +#pragma once +#include "../../Exception.hpp" + +namespace cru::platform::graph::win::direct { +using platform::win::HResultError; +using platform::win::ThrowIfFailed; +} // namespace cru::platform::graph::win::direct \ No newline at end of file diff --git a/include/cru/win/graph/direct/Factory.hpp b/include/cru/win/graph/direct/Factory.hpp new file mode 100644 index 00000000..763d4b2b --- /dev/null +++ b/include/cru/win/graph/direct/Factory.hpp @@ -0,0 +1,58 @@ +#pragma once +#include "Resource.hpp" + +#include "cru/platform/graph/Factory.hpp" + +namespace cru::platform::graph::win::direct { +class DirectGraphFactory : public DirectResource, public virtual IGraphFactory { + public: + DirectGraphFactory(); + + CRU_DELETE_COPY(DirectGraphFactory) + CRU_DELETE_MOVE(DirectGraphFactory) + + ~DirectGraphFactory() override; + + public: + ID3D11Device* GetD3D11Device() const { return d3d11_device_.Get(); } + ID2D1Factory1* GetD2D1Factory() const { return d2d1_factory_.Get(); } + ID2D1Device* GetD2D1Device() const { return d2d1_device_.Get(); } + IDXGIFactory2* GetDxgiFactory() const { return dxgi_factory_.Get(); } + IDWriteFactory* GetDWriteFactory() const { return dwrite_factory_.Get(); } + IDWriteFontCollection* GetSystemFontCollection() const { + return dwrite_system_font_collection_.Get(); + } + + public: + Microsoft::WRL::ComPtr CreateD2D1DeviceContext(); + + // This context should only be used to create graphic resources like brush. + // Because graphic resources can be shared if they are created in the same + // device. + ID2D1DeviceContext* GetDefaultD2D1DeviceContext() { + return d2d1_device_context_.Get(); + } + + public: + std::unique_ptr CreateSolidColorBrush() override; + + std::unique_ptr CreateGeometryBuilder() override; + + std::unique_ptr CreateFont(const std::string_view& font_family, + float font_size) override; + + std::unique_ptr CreateTextLayout(std::shared_ptr font, + std::string text) override; + + private: + Microsoft::WRL::ComPtr d3d11_device_; + // ID2D1Factory1 is a interface only available in Windows 8 and Windows 7 with + // update. It is d2d v1.1. + Microsoft::WRL::ComPtr d2d1_factory_; + Microsoft::WRL::ComPtr d2d1_device_; + Microsoft::WRL::ComPtr d2d1_device_context_; + Microsoft::WRL::ComPtr dxgi_factory_; + Microsoft::WRL::ComPtr dwrite_factory_; + Microsoft::WRL::ComPtr dwrite_system_font_collection_; +}; +} // namespace cru::platform::graph::win::direct diff --git a/include/cru/win/graph/direct/Font.hpp b/include/cru/win/graph/direct/Font.hpp new file mode 100644 index 00000000..ecf9fd81 --- /dev/null +++ b/include/cru/win/graph/direct/Font.hpp @@ -0,0 +1,32 @@ +#pragma once +#include "ComResource.hpp" +#include "Resource.hpp" + +#include "cru/platform/graph/Font.hpp" + +#include + +namespace cru::platform::graph::win::direct { +class DWriteFont : public DirectGraphResource, + public virtual IFont, + public virtual IComResource { + public: + DWriteFont(DirectGraphFactory* factory, const std::string_view& font_family, + float font_size); + + CRU_DELETE_COPY(DWriteFont) + CRU_DELETE_MOVE(DWriteFont) + + ~DWriteFont() override = default; + + public: + IDWriteTextFormat* GetComInterface() const override { + return text_format_.Get(); + } + + float GetFontSize() override; + + private: + Microsoft::WRL::ComPtr text_format_; +}; +} // namespace cru::platform::graph::win::direct diff --git a/include/cru/win/graph/direct/Geometry.hpp b/include/cru/win/graph/direct/Geometry.hpp new file mode 100644 index 00000000..87987d3e --- /dev/null +++ b/include/cru/win/graph/direct/Geometry.hpp @@ -0,0 +1,57 @@ +#pragma once +#include "ComResource.hpp" +#include "Resource.hpp" + +#include "cru/platform/graph/Geometry.hpp" + +namespace cru::platform::graph::win::direct { +class D2DGeometryBuilder : public DirectGraphResource, + public virtual IGeometryBuilder { + public: + explicit D2DGeometryBuilder(DirectGraphFactory* factory); + + CRU_DELETE_COPY(D2DGeometryBuilder) + CRU_DELETE_MOVE(D2DGeometryBuilder) + + ~D2DGeometryBuilder() override = default; + + public: + void BeginFigure(const Point& point) override; + void LineTo(const Point& point) override; + void QuadraticBezierTo(const Point& control_point, + const Point& end_point) override; + void CloseFigure(bool close) override; + + std::unique_ptr Build() override; + + private: + bool IsValid() { return geometry_ != nullptr; } + void CheckValidation(); + + private: + Microsoft::WRL::ComPtr geometry_; + Microsoft::WRL::ComPtr geometry_sink_; +}; + +class D2DGeometry : public DirectGraphResource, + public virtual IGeometry, + public IComResource { + public: + D2DGeometry(DirectGraphFactory* factory, + Microsoft::WRL::ComPtr geometry); + + CRU_DELETE_COPY(D2DGeometry) + CRU_DELETE_MOVE(D2DGeometry) + + ~D2DGeometry() override = default; + + public: + ID2D1Geometry* GetComInterface() const override { return geometry_.Get(); } + + public: + bool FillContains(const Point& point) override; + + private: + Microsoft::WRL::ComPtr geometry_; +}; +} // namespace cru::platform::graph::win::direct diff --git a/include/cru/win/graph/direct/Painter.hpp b/include/cru/win/graph/direct/Painter.hpp new file mode 100644 index 00000000..a50f962d --- /dev/null +++ b/include/cru/win/graph/direct/Painter.hpp @@ -0,0 +1,60 @@ +#pragma once +#include "ComResource.hpp" +#include "Resource.hpp" + +#include "cru/platform/graph/Painter.hpp" + +#include + +namespace cru::platform::graph::win::direct { +class D2DPainter : public DirectResource, + public virtual IPainter, + public virtual IComResource { + public: + explicit D2DPainter(ID2D1RenderTarget* render_target); + + CRU_DELETE_COPY(D2DPainter) + CRU_DELETE_MOVE(D2DPainter) + + ~D2DPainter() override = default; + + public: + ID2D1RenderTarget* GetComInterface() const override { return render_target_; } + + public: + Matrix GetTransform() override; + void SetTransform(const platform::Matrix& matrix) override; + + void Clear(const Color& color) override; + + void StrokeRectangle(const Rect& rectangle, IBrush* brush, + float width) override; + void FillRectangle(const Rect& rectangle, IBrush* brush) override; + + void StrokeGeometry(IGeometry* geometry, IBrush* brush, float width) override; + void FillGeometry(IGeometry* geometry, IBrush* brush) override; + + void DrawText(const Point& offset, ITextLayout* text_layout, + IBrush* brush) override; + + void PushLayer(const Rect& bounds) override; + + void PopLayer() override; + + void EndDraw() override final; + + protected: + virtual void DoEndDraw() = 0; + + private: + bool IsValid() { return is_drawing_; } + void CheckValidation(); + + private: + ID2D1RenderTarget* render_target_; + + std::vector> layers_; + + bool is_drawing_ = true; +}; +} // namespace cru::platform::graph::win::direct diff --git a/include/cru/win/graph/direct/Resource.hpp b/include/cru/win/graph/direct/Resource.hpp new file mode 100644 index 00000000..d0a30dbd --- /dev/null +++ b/include/cru/win/graph/direct/Resource.hpp @@ -0,0 +1,49 @@ +#pragma once +#include "../../WinPreConfig.hpp" + +#include "cru/platform/graph/Resource.hpp" + +#include + +namespace cru::platform::graph::win::direct { +class DirectGraphFactory; + +class DirectResource : public Object, public virtual INativeResource { + public: + static constexpr std::string_view k_platform_id = "Windows Direct"; + + protected: + DirectResource() = default; + + public: + CRU_DELETE_COPY(DirectResource) + CRU_DELETE_MOVE(DirectResource) + + ~DirectResource() override = default; + + public: + std::string_view GetPlatformId() const final { return k_platform_id; } +}; + +class DirectGraphResource : public DirectResource, + public virtual IGraphResource { + protected: + // Param factory can't be null. + explicit DirectGraphResource(DirectGraphFactory* factory); + + public: + CRU_DELETE_COPY(DirectGraphResource) + CRU_DELETE_MOVE(DirectGraphResource) + + ~DirectGraphResource() override = default; + + public: + IGraphFactory* GetGraphFactory() final; + + public: + DirectGraphFactory* GetDirectFactory() const { return factory_; } + + private: + DirectGraphFactory* factory_; +}; +} // namespace cru::platform::graph::win::direct diff --git a/include/cru/win/graph/direct/brush.hpp b/include/cru/win/graph/direct/brush.hpp deleted file mode 100644 index df1debe3..00000000 --- a/include/cru/win/graph/direct/brush.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once -#include "ComResource.hpp" -#include "Resource.hpp" - -#include "cru/platform/graph/Brush.hpp" - -namespace cru::platform::graph::win::direct { -struct ID2DBrush : virtual IBrush { - virtual ID2D1Brush* GetD2DBrushInterface() const = 0; -}; - -class D2DSolidColorBrush : public DirectGraphResource, - public virtual ISolidColorBrush, - public virtual ID2DBrush, - public virtual IComResource { - public: - explicit D2DSolidColorBrush(DirectGraphFactory* factory); - - CRU_DELETE_COPY(D2DSolidColorBrush) - CRU_DELETE_MOVE(D2DSolidColorBrush) - - ~D2DSolidColorBrush() override = default; - - public: - Color GetColor() override { return color_; } - void SetColor(const Color& color) override; - - ID2D1Brush* GetD2DBrushInterface() const override { return brush_.Get(); } - - ID2D1SolidColorBrush* GetComInterface() const override { - return brush_.Get(); - } - - private: - Color color_ = colors::black; - - Microsoft::WRL::ComPtr brush_; -}; -} // namespace cru::platform::graph::win::direct diff --git a/include/cru/win/graph/direct/exception.hpp b/include/cru/win/graph/direct/exception.hpp deleted file mode 100644 index 8b62e8fa..00000000 --- a/include/cru/win/graph/direct/exception.hpp +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once -#include "../../Exception.hpp" - -namespace cru::platform::graph::win::direct { -using platform::win::HResultError; -using platform::win::ThrowIfFailed; -} // namespace cru::platform::graph::win::direct \ No newline at end of file diff --git a/include/cru/win/graph/direct/factory.hpp b/include/cru/win/graph/direct/factory.hpp deleted file mode 100644 index 763d4b2b..00000000 --- a/include/cru/win/graph/direct/factory.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once -#include "Resource.hpp" - -#include "cru/platform/graph/Factory.hpp" - -namespace cru::platform::graph::win::direct { -class DirectGraphFactory : public DirectResource, public virtual IGraphFactory { - public: - DirectGraphFactory(); - - CRU_DELETE_COPY(DirectGraphFactory) - CRU_DELETE_MOVE(DirectGraphFactory) - - ~DirectGraphFactory() override; - - public: - ID3D11Device* GetD3D11Device() const { return d3d11_device_.Get(); } - ID2D1Factory1* GetD2D1Factory() const { return d2d1_factory_.Get(); } - ID2D1Device* GetD2D1Device() const { return d2d1_device_.Get(); } - IDXGIFactory2* GetDxgiFactory() const { return dxgi_factory_.Get(); } - IDWriteFactory* GetDWriteFactory() const { return dwrite_factory_.Get(); } - IDWriteFontCollection* GetSystemFontCollection() const { - return dwrite_system_font_collection_.Get(); - } - - public: - Microsoft::WRL::ComPtr CreateD2D1DeviceContext(); - - // This context should only be used to create graphic resources like brush. - // Because graphic resources can be shared if they are created in the same - // device. - ID2D1DeviceContext* GetDefaultD2D1DeviceContext() { - return d2d1_device_context_.Get(); - } - - public: - std::unique_ptr CreateSolidColorBrush() override; - - std::unique_ptr CreateGeometryBuilder() override; - - std::unique_ptr CreateFont(const std::string_view& font_family, - float font_size) override; - - std::unique_ptr CreateTextLayout(std::shared_ptr font, - std::string text) override; - - private: - Microsoft::WRL::ComPtr d3d11_device_; - // ID2D1Factory1 is a interface only available in Windows 8 and Windows 7 with - // update. It is d2d v1.1. - Microsoft::WRL::ComPtr d2d1_factory_; - Microsoft::WRL::ComPtr d2d1_device_; - Microsoft::WRL::ComPtr d2d1_device_context_; - Microsoft::WRL::ComPtr dxgi_factory_; - Microsoft::WRL::ComPtr dwrite_factory_; - Microsoft::WRL::ComPtr dwrite_system_font_collection_; -}; -} // namespace cru::platform::graph::win::direct diff --git a/include/cru/win/graph/direct/font.hpp b/include/cru/win/graph/direct/font.hpp deleted file mode 100644 index ecf9fd81..00000000 --- a/include/cru/win/graph/direct/font.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once -#include "ComResource.hpp" -#include "Resource.hpp" - -#include "cru/platform/graph/Font.hpp" - -#include - -namespace cru::platform::graph::win::direct { -class DWriteFont : public DirectGraphResource, - public virtual IFont, - public virtual IComResource { - public: - DWriteFont(DirectGraphFactory* factory, const std::string_view& font_family, - float font_size); - - CRU_DELETE_COPY(DWriteFont) - CRU_DELETE_MOVE(DWriteFont) - - ~DWriteFont() override = default; - - public: - IDWriteTextFormat* GetComInterface() const override { - return text_format_.Get(); - } - - float GetFontSize() override; - - private: - Microsoft::WRL::ComPtr text_format_; -}; -} // namespace cru::platform::graph::win::direct diff --git a/include/cru/win/graph/direct/geometry.hpp b/include/cru/win/graph/direct/geometry.hpp deleted file mode 100644 index 87987d3e..00000000 --- a/include/cru/win/graph/direct/geometry.hpp +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once -#include "ComResource.hpp" -#include "Resource.hpp" - -#include "cru/platform/graph/Geometry.hpp" - -namespace cru::platform::graph::win::direct { -class D2DGeometryBuilder : public DirectGraphResource, - public virtual IGeometryBuilder { - public: - explicit D2DGeometryBuilder(DirectGraphFactory* factory); - - CRU_DELETE_COPY(D2DGeometryBuilder) - CRU_DELETE_MOVE(D2DGeometryBuilder) - - ~D2DGeometryBuilder() override = default; - - public: - void BeginFigure(const Point& point) override; - void LineTo(const Point& point) override; - void QuadraticBezierTo(const Point& control_point, - const Point& end_point) override; - void CloseFigure(bool close) override; - - std::unique_ptr Build() override; - - private: - bool IsValid() { return geometry_ != nullptr; } - void CheckValidation(); - - private: - Microsoft::WRL::ComPtr geometry_; - Microsoft::WRL::ComPtr geometry_sink_; -}; - -class D2DGeometry : public DirectGraphResource, - public virtual IGeometry, - public IComResource { - public: - D2DGeometry(DirectGraphFactory* factory, - Microsoft::WRL::ComPtr geometry); - - CRU_DELETE_COPY(D2DGeometry) - CRU_DELETE_MOVE(D2DGeometry) - - ~D2DGeometry() override = default; - - public: - ID2D1Geometry* GetComInterface() const override { return geometry_.Get(); } - - public: - bool FillContains(const Point& point) override; - - private: - Microsoft::WRL::ComPtr geometry_; -}; -} // namespace cru::platform::graph::win::direct diff --git a/include/cru/win/graph/direct/painter.hpp b/include/cru/win/graph/direct/painter.hpp deleted file mode 100644 index a50f962d..00000000 --- a/include/cru/win/graph/direct/painter.hpp +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once -#include "ComResource.hpp" -#include "Resource.hpp" - -#include "cru/platform/graph/Painter.hpp" - -#include - -namespace cru::platform::graph::win::direct { -class D2DPainter : public DirectResource, - public virtual IPainter, - public virtual IComResource { - public: - explicit D2DPainter(ID2D1RenderTarget* render_target); - - CRU_DELETE_COPY(D2DPainter) - CRU_DELETE_MOVE(D2DPainter) - - ~D2DPainter() override = default; - - public: - ID2D1RenderTarget* GetComInterface() const override { return render_target_; } - - public: - Matrix GetTransform() override; - void SetTransform(const platform::Matrix& matrix) override; - - void Clear(const Color& color) override; - - void StrokeRectangle(const Rect& rectangle, IBrush* brush, - float width) override; - void FillRectangle(const Rect& rectangle, IBrush* brush) override; - - void StrokeGeometry(IGeometry* geometry, IBrush* brush, float width) override; - void FillGeometry(IGeometry* geometry, IBrush* brush) override; - - void DrawText(const Point& offset, ITextLayout* text_layout, - IBrush* brush) override; - - void PushLayer(const Rect& bounds) override; - - void PopLayer() override; - - void EndDraw() override final; - - protected: - virtual void DoEndDraw() = 0; - - private: - bool IsValid() { return is_drawing_; } - void CheckValidation(); - - private: - ID2D1RenderTarget* render_target_; - - std::vector> layers_; - - bool is_drawing_ = true; -}; -} // namespace cru::platform::graph::win::direct diff --git a/include/cru/win/graph/direct/resource.hpp b/include/cru/win/graph/direct/resource.hpp deleted file mode 100644 index d0a30dbd..00000000 --- a/include/cru/win/graph/direct/resource.hpp +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once -#include "../../WinPreConfig.hpp" - -#include "cru/platform/graph/Resource.hpp" - -#include - -namespace cru::platform::graph::win::direct { -class DirectGraphFactory; - -class DirectResource : public Object, public virtual INativeResource { - public: - static constexpr std::string_view k_platform_id = "Windows Direct"; - - protected: - DirectResource() = default; - - public: - CRU_DELETE_COPY(DirectResource) - CRU_DELETE_MOVE(DirectResource) - - ~DirectResource() override = default; - - public: - std::string_view GetPlatformId() const final { return k_platform_id; } -}; - -class DirectGraphResource : public DirectResource, - public virtual IGraphResource { - protected: - // Param factory can't be null. - explicit DirectGraphResource(DirectGraphFactory* factory); - - public: - CRU_DELETE_COPY(DirectGraphResource) - CRU_DELETE_MOVE(DirectGraphResource) - - ~DirectGraphResource() override = default; - - public: - IGraphFactory* GetGraphFactory() final; - - public: - DirectGraphFactory* GetDirectFactory() const { return factory_; } - - private: - DirectGraphFactory* factory_; -}; -} // namespace cru::platform::graph::win::direct diff --git a/include/cru/win/native/Base.hpp b/include/cru/win/native/Base.hpp new file mode 100644 index 00000000..a50c6dd1 --- /dev/null +++ b/include/cru/win/native/Base.hpp @@ -0,0 +1,19 @@ +#pragma once +#include "../WinPreConfig.hpp" + +#include "cru/common/Base.hpp" + +namespace cru::platform::native::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 diff --git a/include/cru/win/native/Cursor.hpp b/include/cru/win/native/Cursor.hpp new file mode 100644 index 00000000..152374d8 --- /dev/null +++ b/include/cru/win/native/Cursor.hpp @@ -0,0 +1,47 @@ +#pragma once +#include "Resource.hpp" + +#include "cru/platform/native/Cursor.hpp" + +#include + +namespace cru::platform::native::win { +class WinCursor : public WinNativeResource, public virtual ICursor { + public: + WinCursor(HCURSOR handle, bool auto_destroy); + + CRU_DELETE_COPY(WinCursor) + CRU_DELETE_MOVE(WinCursor) + + ~WinCursor() override; + + public: + HCURSOR GetHandle() const { return handle_; } + + private: + HCURSOR handle_; + bool auto_destroy_; +}; + +class WinCursorManager : public WinNativeResource, + public virtual ICursorManager { + public: + WinCursorManager(); + + CRU_DELETE_COPY(WinCursorManager) + CRU_DELETE_MOVE(WinCursorManager) + + ~WinCursorManager() override = default; + + public: + std::shared_ptr GetSystemWinCursor(SystemCursorType type); + + std::shared_ptr GetSystemCursor(SystemCursorType type) override { + return std::static_pointer_cast(GetSystemWinCursor(type)); + } + + private: + std::shared_ptr sys_arrow_; + std::shared_ptr sys_hand_; +}; +} // namespace cru::platform::native::win diff --git a/include/cru/win/native/Exception.hpp b/include/cru/win/native/Exception.hpp new file mode 100644 index 00000000..6a5265c1 --- /dev/null +++ b/include/cru/win/native/Exception.hpp @@ -0,0 +1,7 @@ +#pragma once +#include "../Exception.hpp" + +namespace cru::platform::native::win { +using platform::win::Win32Error; +using platform::win::HResultError; +} // namespace cru::platform::native::win diff --git a/include/cru/win/native/Keyboard.hpp b/include/cru/win/native/Keyboard.hpp new file mode 100644 index 00000000..790e0015 --- /dev/null +++ b/include/cru/win/native/Keyboard.hpp @@ -0,0 +1,9 @@ +#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/Resource.hpp b/include/cru/win/native/Resource.hpp new file mode 100644 index 00000000..7afaca0f --- /dev/null +++ b/include/cru/win/native/Resource.hpp @@ -0,0 +1,23 @@ +#pragma once +#include "Base.hpp" + +#include "cru/platform/Resource.hpp" + +namespace cru::platform::native::win { +class WinNativeResource : public Object, public virtual INativeResource { + public: + static constexpr std::string_view k_platform_id = "Windows"; + + protected: + WinNativeResource() = default; + + public: + CRU_DELETE_COPY(WinNativeResource) + CRU_DELETE_MOVE(WinNativeResource) + + ~WinNativeResource() override = default; + + public: + std::string_view GetPlatformId() const final { return k_platform_id; } +}; +} // namespace cru::platform::native::win diff --git a/include/cru/win/native/Window.hpp b/include/cru/win/native/Window.hpp new file mode 100644 index 00000000..80bee39e --- /dev/null +++ b/include/cru/win/native/Window.hpp @@ -0,0 +1,174 @@ +#pragma once +#include "Resource.hpp" + +#include "cru/platform/native/Window.hpp" +#include "WindowNativeMessageEventArgs.hpp" + +#include + +namespace cru::platform::native::win { +class WinNativeWindow : public WinNativeResource, public virtual INativeWindow { + public: + WinNativeWindow(WinUiApplication* application, WindowClass* window_class, + DWORD window_style, WinNativeWindow* parent); + + CRU_DELETE_COPY(WinNativeWindow) + CRU_DELETE_MOVE(WinNativeWindow) + + ~WinNativeWindow() override; + + public: + std::shared_ptr GetResolver() override { + return std::static_pointer_cast(resolver_); + } + + void Close() override; + + WinNativeWindow* GetParent() override { return parent_window_; } + + bool IsVisible() override; + void SetVisible(bool is_visible) override; + + Size GetClientSize() override; + void SetClientSize(const Size& size) override; + + // Get the rect of the window containing frame. + // The lefttop of the rect is relative to screen lefttop. + Rect GetWindowRect() override; + + // Set the rect of the window containing frame. + // The lefttop of the rect is relative to screen lefttop. + void SetWindowRect(const Rect& rect) override; + + Point GetMousePosition() override; + + bool CaptureMouse() override; + bool ReleaseMouse() override; + + void RequestRepaint() override; + std::unique_ptr BeginPaint() override; + + void SetCursor(std::shared_ptr cursor) override; + + IEvent* DestroyEvent() override { return &destroy_event_; } + IEvent* PaintEvent() override { return &paint_event_; } + IEvent* ResizeEvent() override { return &resize_event_; } + IEvent* FocusEvent() override { return &focus_event_; } + IEvent* MouseEnterLeaveEvent() override { + return &mouse_enter_leave_event_; + } + IEvent* MouseMoveEvent() override { return &mouse_move_event_; } + IEvent* MouseDownEvent() + override { + return &mouse_down_event_; + } + IEvent* MouseUpEvent() + override { + return &mouse_up_event_; + } + IEvent* KeyDownEvent() override { + return &key_down_event_; + } + IEvent* KeyUpEvent() override { + return &key_up_event_; + } + + IEvent* NativeMessageEvent() { + return &native_message_event_; + } + + // 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 { + return window_render_target_.get(); + } + + private: + // Get the client rect in pixel. + RECT GetClientRectPixel(); + + //*************** region: native messages *************** + + void OnDestroyInternal(); + void OnPaintInternal(); + void OnResizeInternal(int new_width, int new_height); + + void OnSetFocusInternal(); + void OnKillFocusInternal(); + + void OnMouseMoveInternal(POINT point); + void OnMouseLeaveInternal(); + void OnMouseDownInternal(platform::native::MouseButton button, POINT point); + void OnMouseUpInternal(platform::native::MouseButton button, POINT point); + + void OnMouseWheelInternal(short delta, POINT point); + void OnKeyDownInternal(int virtual_code); + void OnKeyUpInternal(int virtual_code); + + void OnActivatedInternal(); + void OnDeactivatedInternal(); + + private: + WinUiApplication* application_; + + // when delete is called first, it set this to true to indicate + // destroy message handler not to double delete this instance; + // when destroy handler is called first (by user action or method + // Close), it set this to true to indicate delete not call Close + // again. + bool sync_flag_ = false; + + std::shared_ptr resolver_; + + HWND hwnd_; + WinNativeWindow* parent_window_; + + bool has_focus_ = false; + bool is_mouse_in_ = false; + + std::unique_ptr window_render_target_; + + std::shared_ptr cursor_; + + Event destroy_event_; + Event paint_event_; + Event resize_event_; + Event focus_event_; + Event mouse_enter_leave_event_; + Event mouse_move_event_; + Event mouse_down_event_; + Event mouse_up_event_; + Event key_down_event_; + Event key_up_event_; + + Event 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 resolver); +} // namespace cru::platform::native::win diff --git a/include/cru/win/native/base.hpp b/include/cru/win/native/base.hpp deleted file mode 100644 index a50c6dd1..00000000 --- a/include/cru/win/native/base.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -#include "../WinPreConfig.hpp" - -#include "cru/common/Base.hpp" - -namespace cru::platform::native::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 diff --git a/include/cru/win/native/cursor.hpp b/include/cru/win/native/cursor.hpp deleted file mode 100644 index 152374d8..00000000 --- a/include/cru/win/native/cursor.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once -#include "Resource.hpp" - -#include "cru/platform/native/Cursor.hpp" - -#include - -namespace cru::platform::native::win { -class WinCursor : public WinNativeResource, public virtual ICursor { - public: - WinCursor(HCURSOR handle, bool auto_destroy); - - CRU_DELETE_COPY(WinCursor) - CRU_DELETE_MOVE(WinCursor) - - ~WinCursor() override; - - public: - HCURSOR GetHandle() const { return handle_; } - - private: - HCURSOR handle_; - bool auto_destroy_; -}; - -class WinCursorManager : public WinNativeResource, - public virtual ICursorManager { - public: - WinCursorManager(); - - CRU_DELETE_COPY(WinCursorManager) - CRU_DELETE_MOVE(WinCursorManager) - - ~WinCursorManager() override = default; - - public: - std::shared_ptr GetSystemWinCursor(SystemCursorType type); - - std::shared_ptr GetSystemCursor(SystemCursorType type) override { - return std::static_pointer_cast(GetSystemWinCursor(type)); - } - - private: - std::shared_ptr sys_arrow_; - std::shared_ptr sys_hand_; -}; -} // namespace cru::platform::native::win diff --git a/include/cru/win/native/exception.hpp b/include/cru/win/native/exception.hpp deleted file mode 100644 index 6a5265c1..00000000 --- a/include/cru/win/native/exception.hpp +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once -#include "../Exception.hpp" - -namespace cru::platform::native::win { -using platform::win::Win32Error; -using platform::win::HResultError; -} // namespace cru::platform::native::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/resource.hpp b/include/cru/win/native/resource.hpp deleted file mode 100644 index 7afaca0f..00000000 --- a/include/cru/win/native/resource.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "Base.hpp" - -#include "cru/platform/Resource.hpp" - -namespace cru::platform::native::win { -class WinNativeResource : public Object, public virtual INativeResource { - public: - static constexpr std::string_view k_platform_id = "Windows"; - - protected: - WinNativeResource() = default; - - public: - CRU_DELETE_COPY(WinNativeResource) - CRU_DELETE_MOVE(WinNativeResource) - - ~WinNativeResource() override = default; - - public: - std::string_view GetPlatformId() const final { return k_platform_id; } -}; -} // namespace cru::platform::native::win diff --git a/include/cru/win/native/window.hpp b/include/cru/win/native/window.hpp deleted file mode 100644 index 80bee39e..00000000 --- a/include/cru/win/native/window.hpp +++ /dev/null @@ -1,174 +0,0 @@ -#pragma once -#include "Resource.hpp" - -#include "cru/platform/native/Window.hpp" -#include "WindowNativeMessageEventArgs.hpp" - -#include - -namespace cru::platform::native::win { -class WinNativeWindow : public WinNativeResource, public virtual INativeWindow { - public: - WinNativeWindow(WinUiApplication* application, WindowClass* window_class, - DWORD window_style, WinNativeWindow* parent); - - CRU_DELETE_COPY(WinNativeWindow) - CRU_DELETE_MOVE(WinNativeWindow) - - ~WinNativeWindow() override; - - public: - std::shared_ptr GetResolver() override { - return std::static_pointer_cast(resolver_); - } - - void Close() override; - - WinNativeWindow* GetParent() override { return parent_window_; } - - bool IsVisible() override; - void SetVisible(bool is_visible) override; - - Size GetClientSize() override; - void SetClientSize(const Size& size) override; - - // Get the rect of the window containing frame. - // The lefttop of the rect is relative to screen lefttop. - Rect GetWindowRect() override; - - // Set the rect of the window containing frame. - // The lefttop of the rect is relative to screen lefttop. - void SetWindowRect(const Rect& rect) override; - - Point GetMousePosition() override; - - bool CaptureMouse() override; - bool ReleaseMouse() override; - - void RequestRepaint() override; - std::unique_ptr BeginPaint() override; - - void SetCursor(std::shared_ptr cursor) override; - - IEvent* DestroyEvent() override { return &destroy_event_; } - IEvent* PaintEvent() override { return &paint_event_; } - IEvent* ResizeEvent() override { return &resize_event_; } - IEvent* FocusEvent() override { return &focus_event_; } - IEvent* MouseEnterLeaveEvent() override { - return &mouse_enter_leave_event_; - } - IEvent* MouseMoveEvent() override { return &mouse_move_event_; } - IEvent* MouseDownEvent() - override { - return &mouse_down_event_; - } - IEvent* MouseUpEvent() - override { - return &mouse_up_event_; - } - IEvent* KeyDownEvent() override { - return &key_down_event_; - } - IEvent* KeyUpEvent() override { - return &key_up_event_; - } - - IEvent* NativeMessageEvent() { - return &native_message_event_; - } - - // 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 { - return window_render_target_.get(); - } - - private: - // Get the client rect in pixel. - RECT GetClientRectPixel(); - - //*************** region: native messages *************** - - void OnDestroyInternal(); - void OnPaintInternal(); - void OnResizeInternal(int new_width, int new_height); - - void OnSetFocusInternal(); - void OnKillFocusInternal(); - - void OnMouseMoveInternal(POINT point); - void OnMouseLeaveInternal(); - void OnMouseDownInternal(platform::native::MouseButton button, POINT point); - void OnMouseUpInternal(platform::native::MouseButton button, POINT point); - - void OnMouseWheelInternal(short delta, POINT point); - void OnKeyDownInternal(int virtual_code); - void OnKeyUpInternal(int virtual_code); - - void OnActivatedInternal(); - void OnDeactivatedInternal(); - - private: - WinUiApplication* application_; - - // when delete is called first, it set this to true to indicate - // destroy message handler not to double delete this instance; - // when destroy handler is called first (by user action or method - // Close), it set this to true to indicate delete not call Close - // again. - bool sync_flag_ = false; - - std::shared_ptr resolver_; - - HWND hwnd_; - WinNativeWindow* parent_window_; - - bool has_focus_ = false; - bool is_mouse_in_ = false; - - std::unique_ptr window_render_target_; - - std::shared_ptr cursor_; - - Event destroy_event_; - Event paint_event_; - Event resize_event_; - Event focus_event_; - Event mouse_enter_leave_event_; - Event mouse_move_event_; - Event mouse_down_event_; - Event mouse_up_event_; - Event key_down_event_; - Event key_up_event_; - - Event 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 resolver); -} // namespace cru::platform::native::win diff --git a/include/cru/win/string.hpp b/include/cru/win/string.hpp deleted file mode 100644 index 3d68cff7..00000000 --- a/include/cru/win/string.hpp +++ /dev/null @@ -1,107 +0,0 @@ -/* -Because the text encoding problem on Windows, here I write some functions -related to text encoding. The utf-8 and utf-16 conversion function is provided -by win32 api. However win32 api does not provide any function about charactor -iteration or index by code point. (At least I haven't found.) I don't use icu -because it is not easy to build it on Windows and the bundled version in Windows -(https://docs.microsoft.com/en-us/windows/win32/intl/international-components-for-unicode--icu-) -is only available after Windows 10 Creators Update. - -Luckily, both utf-8 and utf-16 encoding are easy to learn and program with if we -only do simple iteration rather than do much sophisticated work about -complicated error situations. (And I learn the internal of the encoding by the -way.) -*/ - -#pragma once -#include "WinPreConfig.hpp" - -#include "cru/common/Base.hpp" - -#include -#include -#include -#include - -namespace cru::platform::win { -std::string ToUtf8String(const std::wstring_view& string); -std::wstring ToUtf16String(const std::string_view& string); - -inline bool IsSurrogatePair(wchar_t c) { return c >= 0xD800 && c <= 0xDFFF; } - -inline bool IsSurrogatePairLeading(wchar_t c) { - return c >= 0xD800 && c <= 0xDBFF; -} - -inline bool IsSurrogatePairTrailing(wchar_t c) { - return c >= 0xDC00 && c <= 0xDFFF; -} - -using CodePoint = std::int32_t; -constexpr CodePoint k_code_point_end = -1; - -class TextEncodeException : public std::runtime_error { - public: - using runtime_error::runtime_error; -}; - -class Utf8Iterator : public Object { - public: - Utf8Iterator(const std::string_view& string) : string_(string) {} - - CRU_DEFAULT_COPY(Utf8Iterator) - CRU_DEFAULT_MOVE(Utf8Iterator) - - ~Utf8Iterator() = default; - - public: - void SetToHead() { position_ = 0; } - - // Advance current position and get next code point. Return k_code_point_end - // if there is no next code unit(point). Throw TextEncodeException if decoding - // fails. - CodePoint Next(); - - int CurrentPosition() const { return this->position_; } - - private: - std::string_view string_; - int position_ = 0; -}; - -class Utf16Iterator : public Object { - static_assert( - sizeof(wchar_t) == 2, - "Emmm, according to my knowledge, wchar_t should be 2-length on " - "Windows. If not, Utf16 will be broken."); - - public: - Utf16Iterator(const std::wstring_view& string) : string_(string) {} - - CRU_DEFAULT_COPY(Utf16Iterator) - CRU_DEFAULT_MOVE(Utf16Iterator) - - ~Utf16Iterator() = default; - - public: - void SetToHead() { position_ = 0; } - - // Advance current position and get next code point. Return k_code_point_end - // if there is no next code unit(point). Throw TextEncodeException if decoding - // fails. - CodePoint Next(); - - int CurrentPosition() const { return this->position_; } - - private: - std::wstring_view string_; - int position_ = 0; -}; - -int IndexUtf8ToUtf16(const std::string_view& utf8_string, int utf8_index, - const std::wstring_view& utf16_string); - -int IndexUtf16ToUtf8(const std::wstring_view& utf16_string, int utf16_index, - const std::string_view& utf8_string); - -} // namespace cru::platform::win diff --git a/src/common/Logger.cpp b/src/common/Logger.cpp new file mode 100644 index 00000000..ed9f9e64 --- /dev/null +++ b/src/common/Logger.cpp @@ -0,0 +1,65 @@ +#include "cru/common/Logger.hpp" + +#include +#include +#include +#include +#include + +namespace cru::log { +namespace { +Logger *CreateLogger() { + const auto logger = new Logger(); + logger->AddSource(std::make_unique()); + return logger; +} +} // namespace + +Logger *Logger::GetInstance() { + static std::unique_ptr logger{CreateLogger()}; + return logger.get(); +} + +void Logger::AddSource(std::unique_ptr source) { + sources_.push_back(std::move(source)); +} + +void Logger::RemoveSource(ILogSource *source) { + sources_.remove_if([source](const std::unique_ptr &s) { + return s.get() == source; + }); +} + +namespace { +std::string_view LogLevelToString(LogLevel level) { + switch (level) { + case LogLevel::Debug: + return "DEBUG"; + case LogLevel::Info: + return "INFO"; + case LogLevel::Warn: + return "WARN"; + case LogLevel::Error: + return "ERROR"; + default: + std::abort(); + } +} +} // namespace + +void Logger::Log(LogLevel level, const std::string_view &s) { +#ifndef CRU_DEBUG + if (level == LogLevel::Debug) { + return; + } +#endif + for (const auto &source : sources_) { + auto now = std::time(nullptr); + std::array buffer; + std::strftime(buffer.data(), 50, "%c", std::localtime(&now)); + + source->Write(level, fmt::format("[{}] {}: {}\n", buffer.data(), + LogLevelToString(level), s)); + } +} +} // namespace cru::log diff --git a/src/common/logger.cpp b/src/common/logger.cpp deleted file mode 100644 index ed9f9e64..00000000 --- a/src/common/logger.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "cru/common/Logger.hpp" - -#include -#include -#include -#include -#include - -namespace cru::log { -namespace { -Logger *CreateLogger() { - const auto logger = new Logger(); - logger->AddSource(std::make_unique()); - return logger; -} -} // namespace - -Logger *Logger::GetInstance() { - static std::unique_ptr logger{CreateLogger()}; - return logger.get(); -} - -void Logger::AddSource(std::unique_ptr source) { - sources_.push_back(std::move(source)); -} - -void Logger::RemoveSource(ILogSource *source) { - sources_.remove_if([source](const std::unique_ptr &s) { - return s.get() == source; - }); -} - -namespace { -std::string_view LogLevelToString(LogLevel level) { - switch (level) { - case LogLevel::Debug: - return "DEBUG"; - case LogLevel::Info: - return "INFO"; - case LogLevel::Warn: - return "WARN"; - case LogLevel::Error: - return "ERROR"; - default: - std::abort(); - } -} -} // namespace - -void Logger::Log(LogLevel level, const std::string_view &s) { -#ifndef CRU_DEBUG - if (level == LogLevel::Debug) { - return; - } -#endif - for (const auto &source : sources_) { - auto now = std::time(nullptr); - std::array buffer; - std::strftime(buffer.data(), 50, "%c", std::localtime(&now)); - - source->Write(level, fmt::format("[{}] {}: {}\n", buffer.data(), - LogLevelToString(level), s)); - } -} -} // namespace cru::log diff --git a/src/ui/Control.cpp b/src/ui/Control.cpp new file mode 100644 index 00000000..cd1367fe --- /dev/null +++ b/src/ui/Control.cpp @@ -0,0 +1,130 @@ +#include "cru/ui/Control.hpp" + +#include "cru/platform/native/Cursor.hpp" +#include "cru/platform/native/UiApplication.hpp" +#include "cru/ui/Base.hpp" +#include "cru/ui/UiHost.hpp" +#include "RoutedEventDispatch.hpp" + +#include + +namespace cru::ui { +using platform::native::ICursor; +using platform::native::IUiApplication; +using platform::native::SystemCursorType; + +Control::Control() { + MouseEnterEvent()->Direct()->AddHandler([this](event::MouseEventArgs&) { + this->is_mouse_over_ = true; + this->OnMouseHoverChange(true); + }); + + MouseLeaveEvent()->Direct()->AddHandler([this](event::MouseEventArgs&) { + this->is_mouse_over_ = false; + this->OnMouseHoverChange(true); + }); +} + +void Control::_SetParent(Control* parent) { + const auto old_parent = GetParent(); + parent_ = parent; + const auto new_parent = GetParent(); + if (old_parent != new_parent) OnParentChanged(old_parent, new_parent); +} + +void Control::_SetDescendantUiHost(UiHost* host) { + if (host == nullptr && ui_host_ == nullptr) return; + + // You can only attach or detach window. + Expects((host != nullptr && ui_host_ == nullptr) || + (host == nullptr && ui_host_ != nullptr)); + + if (host == nullptr) { + const auto old = ui_host_; + TraverseDescendants([old](Control* control) { + control->ui_host_ = nullptr; + control->OnDetachFromHost(old); + }); + } else + TraverseDescendants([host](Control* control) { + control->ui_host_ = host; + control->OnAttachToHost(host); + }); +} + +void Control::TraverseDescendants( + const std::function& predicate) { + _TraverseDescendants(this, predicate); +} + +void Control::_TraverseDescendants( + Control* control, const std::function& predicate) { + predicate(control); + for (auto c : control->GetChildren()) _TraverseDescendants(c, predicate); +} + +bool Control::RequestFocus() { + auto host = GetUiHost(); + if (host == nullptr) return false; + + return host->RequestFocusFor(this); +} + +bool Control::HasFocus() { + auto host = GetUiHost(); + if (host == nullptr) return false; + + return host->GetFocusControl() == this; +} + +bool Control::CaptureMouse() { + auto host = GetUiHost(); + if (host == nullptr) return false; + + return host->CaptureMouseFor(this); +} + +bool Control::ReleaseMouse() { + auto host = GetUiHost(); + if (host == nullptr) return false; + + return host->CaptureMouseFor(nullptr); +} + +bool Control::IsMouseCaptured() { + auto host = GetUiHost(); + if (host == nullptr) return false; + + return host->GetMouseCaptureControl() == this; +} + +std::shared_ptr Control::GetCursor() { return cursor_; } + +std::shared_ptr Control::GetInheritedCursor() { + Control* control = this; + while (control != nullptr) { + const auto cursor = control->GetCursor(); + if (cursor != nullptr) return cursor; + control = control->GetParent(); + } + return IUiApplication::GetInstance()->GetCursorManager()->GetSystemCursor( + SystemCursorType::Arrow); +} + +void Control::SetCursor(std::shared_ptr cursor) { + cursor_ = std::move(cursor); + const auto host = GetUiHost(); + if (host != nullptr) { + host->UpdateCursor(); + } +} + +void Control::OnParentChanged(Control* old_parent, Control* new_parent) { + CRU_UNUSED(old_parent) + CRU_UNUSED(new_parent) +} + +void Control::OnAttachToHost(UiHost* host) { CRU_UNUSED(host) } + +void Control::OnDetachFromHost(UiHost* host) { CRU_UNUSED(host) } +} // namespace cru::ui diff --git a/src/ui/Helper.cpp b/src/ui/Helper.cpp new file mode 100644 index 00000000..6f67e701 --- /dev/null +++ b/src/ui/Helper.cpp @@ -0,0 +1,15 @@ +#include "Helper.hpp" + +#include "cru/platform/graph/Factory.hpp" +#include "cru/platform/native/UiApplication.hpp" + +namespace cru::ui { +using cru::platform::graph::IGraphFactory; +using cru::platform::native::IUiApplication; + +IGraphFactory* GetGraphFactory() { + return IUiApplication::GetInstance()->GetGraphFactory(); +} + +IUiApplication* GetUiApplication() { return IUiApplication::GetInstance(); } +} // namespace cru::ui diff --git a/src/ui/Helper.hpp b/src/ui/Helper.hpp new file mode 100644 index 00000000..6923852f --- /dev/null +++ b/src/ui/Helper.hpp @@ -0,0 +1,17 @@ +#pragma once +#include "cru/ui/Base.hpp" + +namespace cru::platform { +namespace graph { +struct IGraphFactory; +} +namespace native { +struct ICursor; +struct IUiApplication; +} // namespace native +} // namespace cru::platform + +namespace cru::ui { +cru::platform::graph::IGraphFactory* GetGraphFactory(); +cru::platform::native::IUiApplication* GetUiApplication(); +} // namespace cru::ui diff --git a/src/ui/Window.cpp b/src/ui/Window.cpp new file mode 100644 index 00000000..de7044dd --- /dev/null +++ b/src/ui/Window.cpp @@ -0,0 +1,28 @@ +#include "cru/ui/Window.hpp" + +#include "cru/ui/render/WindowRenderObject.hpp" +#include "cru/ui/UiHost.hpp" + +namespace cru::ui { +Window* Window::CreateOverlapped() { + return new Window(tag_overlapped_constructor{}); +} + +Window::Window(tag_overlapped_constructor) { + managed_ui_host_ = std::make_unique(this); +} + +Window::~Window() { + // explicit destroy ui host first. + managed_ui_host_.reset(); +} + +std::string_view Window::GetControlType() const { return control_type; } + +render::RenderObject* Window::GetRenderObject() const { return render_object_; } + +void Window::OnChildChanged(Control* old_child, Control* new_child) { + if (old_child) render_object_->RemoveChild(0); + if (new_child) render_object_->AddChild(new_child->GetRenderObject(), 0); +} +} // namespace cru::ui diff --git a/src/ui/control.cpp b/src/ui/control.cpp deleted file mode 100644 index cd1367fe..00000000 --- a/src/ui/control.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include "cru/ui/Control.hpp" - -#include "cru/platform/native/Cursor.hpp" -#include "cru/platform/native/UiApplication.hpp" -#include "cru/ui/Base.hpp" -#include "cru/ui/UiHost.hpp" -#include "RoutedEventDispatch.hpp" - -#include - -namespace cru::ui { -using platform::native::ICursor; -using platform::native::IUiApplication; -using platform::native::SystemCursorType; - -Control::Control() { - MouseEnterEvent()->Direct()->AddHandler([this](event::MouseEventArgs&) { - this->is_mouse_over_ = true; - this->OnMouseHoverChange(true); - }); - - MouseLeaveEvent()->Direct()->AddHandler([this](event::MouseEventArgs&) { - this->is_mouse_over_ = false; - this->OnMouseHoverChange(true); - }); -} - -void Control::_SetParent(Control* parent) { - const auto old_parent = GetParent(); - parent_ = parent; - const auto new_parent = GetParent(); - if (old_parent != new_parent) OnParentChanged(old_parent, new_parent); -} - -void Control::_SetDescendantUiHost(UiHost* host) { - if (host == nullptr && ui_host_ == nullptr) return; - - // You can only attach or detach window. - Expects((host != nullptr && ui_host_ == nullptr) || - (host == nullptr && ui_host_ != nullptr)); - - if (host == nullptr) { - const auto old = ui_host_; - TraverseDescendants([old](Control* control) { - control->ui_host_ = nullptr; - control->OnDetachFromHost(old); - }); - } else - TraverseDescendants([host](Control* control) { - control->ui_host_ = host; - control->OnAttachToHost(host); - }); -} - -void Control::TraverseDescendants( - const std::function& predicate) { - _TraverseDescendants(this, predicate); -} - -void Control::_TraverseDescendants( - Control* control, const std::function& predicate) { - predicate(control); - for (auto c : control->GetChildren()) _TraverseDescendants(c, predicate); -} - -bool Control::RequestFocus() { - auto host = GetUiHost(); - if (host == nullptr) return false; - - return host->RequestFocusFor(this); -} - -bool Control::HasFocus() { - auto host = GetUiHost(); - if (host == nullptr) return false; - - return host->GetFocusControl() == this; -} - -bool Control::CaptureMouse() { - auto host = GetUiHost(); - if (host == nullptr) return false; - - return host->CaptureMouseFor(this); -} - -bool Control::ReleaseMouse() { - auto host = GetUiHost(); - if (host == nullptr) return false; - - return host->CaptureMouseFor(nullptr); -} - -bool Control::IsMouseCaptured() { - auto host = GetUiHost(); - if (host == nullptr) return false; - - return host->GetMouseCaptureControl() == this; -} - -std::shared_ptr Control::GetCursor() { return cursor_; } - -std::shared_ptr Control::GetInheritedCursor() { - Control* control = this; - while (control != nullptr) { - const auto cursor = control->GetCursor(); - if (cursor != nullptr) return cursor; - control = control->GetParent(); - } - return IUiApplication::GetInstance()->GetCursorManager()->GetSystemCursor( - SystemCursorType::Arrow); -} - -void Control::SetCursor(std::shared_ptr cursor) { - cursor_ = std::move(cursor); - const auto host = GetUiHost(); - if (host != nullptr) { - host->UpdateCursor(); - } -} - -void Control::OnParentChanged(Control* old_parent, Control* new_parent) { - CRU_UNUSED(old_parent) - CRU_UNUSED(new_parent) -} - -void Control::OnAttachToHost(UiHost* host) { CRU_UNUSED(host) } - -void Control::OnDetachFromHost(UiHost* host) { CRU_UNUSED(host) } -} // namespace cru::ui diff --git a/src/ui/controls/Button.cpp b/src/ui/controls/Button.cpp new file mode 100644 index 00000000..6f6af878 --- /dev/null +++ b/src/ui/controls/Button.cpp @@ -0,0 +1,73 @@ +#include "cru/ui/controls/Button.hpp" +#include + +#include "../Helper.hpp" +#include "cru/platform/graph/Brush.hpp" +#include "cru/platform/native/Cursor.hpp" +#include "cru/platform/native/UiApplication.hpp" +#include "cru/ui/render/BorderRenderObject.hpp" +#include "cru/ui/UiManager.hpp" +#include "cru/ui/Window.hpp" + +namespace cru::ui::controls { +using cru::platform::native::SystemCursorType; + +namespace { +void Set(render::BorderRenderObject* o, const ButtonStateStyle& s) { + o->SetBorderBrush(s.border_brush); + o->SetBorderThickness(s.border_thickness); + o->SetBorderRadius(s.border_radius); + o->SetForegroundBrush(s.foreground_brush); + o->SetBackgroundBrush(s.background_brush); +} + +std::shared_ptr GetSystemCursor( + SystemCursorType type) { + return GetUiApplication()->GetCursorManager()->GetSystemCursor(type); +} +} // namespace + +Button::Button() : click_detector_(this) { + style_ = UiManager::GetInstance()->GetThemeResources()->button_style; + + render_object_ = std::make_unique(); + render_object_->SetAttachedControl(this); + Set(render_object_.get(), style_.normal); + render_object_->SetBorderEnabled(true); + + click_detector_.StateChangeEvent()->AddHandler( + [this](const ClickState& state) { + switch (state) { + case ClickState::None: + Set(render_object_.get(), style_.normal); + SetCursor(GetSystemCursor(SystemCursorType::Arrow)); + break; + case ClickState::Hover: + Set(render_object_.get(), style_.hover); + SetCursor(GetSystemCursor(SystemCursorType::Hand)); + break; + case ClickState::Press: + Set(render_object_.get(), style_.press); + SetCursor(GetSystemCursor(SystemCursorType::Hand)); + break; + case ClickState::PressInactive: + Set(render_object_.get(), style_.press_cancel); + SetCursor(GetSystemCursor(SystemCursorType::Arrow)); + break; + } + }); +} + +Button::~Button() = default; + +render::RenderObject* Button::GetRenderObject() const { + return render_object_.get(); +} + +void Button::OnChildChanged(Control* old_child, Control* new_child) { + if (old_child != nullptr) render_object_->RemoveChild(0); + if (new_child != nullptr) + render_object_->AddChild(new_child->GetRenderObject(), 0); +} + +} // namespace cru::ui::controls diff --git a/src/ui/controls/Container.cpp b/src/ui/controls/Container.cpp new file mode 100644 index 00000000..de58ee64 --- /dev/null +++ b/src/ui/controls/Container.cpp @@ -0,0 +1,18 @@ +#include "cru/ui/controls/Container.hpp" + +#include "cru/platform/graph/Factory.hpp" +#include "cru/ui/render/BorderRenderObject.hpp" + +namespace cru::ui::controls { +Container::Container() { + render_object_ = std::make_unique(); + render_object_->SetBorderEnabled(false); +} + +Container::~Container() = default; + +void Container::OnChildChanged(Control*, Control* new_child) { + render_object_->RemoveChild(0); + render_object_->AddChild(new_child->GetRenderObject(), 0); +} +} // namespace cru::ui::controls diff --git a/src/ui/controls/button.cpp b/src/ui/controls/button.cpp deleted file mode 100644 index 6f6af878..00000000 --- a/src/ui/controls/button.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include "cru/ui/controls/Button.hpp" -#include - -#include "../Helper.hpp" -#include "cru/platform/graph/Brush.hpp" -#include "cru/platform/native/Cursor.hpp" -#include "cru/platform/native/UiApplication.hpp" -#include "cru/ui/render/BorderRenderObject.hpp" -#include "cru/ui/UiManager.hpp" -#include "cru/ui/Window.hpp" - -namespace cru::ui::controls { -using cru::platform::native::SystemCursorType; - -namespace { -void Set(render::BorderRenderObject* o, const ButtonStateStyle& s) { - o->SetBorderBrush(s.border_brush); - o->SetBorderThickness(s.border_thickness); - o->SetBorderRadius(s.border_radius); - o->SetForegroundBrush(s.foreground_brush); - o->SetBackgroundBrush(s.background_brush); -} - -std::shared_ptr GetSystemCursor( - SystemCursorType type) { - return GetUiApplication()->GetCursorManager()->GetSystemCursor(type); -} -} // namespace - -Button::Button() : click_detector_(this) { - style_ = UiManager::GetInstance()->GetThemeResources()->button_style; - - render_object_ = std::make_unique(); - render_object_->SetAttachedControl(this); - Set(render_object_.get(), style_.normal); - render_object_->SetBorderEnabled(true); - - click_detector_.StateChangeEvent()->AddHandler( - [this](const ClickState& state) { - switch (state) { - case ClickState::None: - Set(render_object_.get(), style_.normal); - SetCursor(GetSystemCursor(SystemCursorType::Arrow)); - break; - case ClickState::Hover: - Set(render_object_.get(), style_.hover); - SetCursor(GetSystemCursor(SystemCursorType::Hand)); - break; - case ClickState::Press: - Set(render_object_.get(), style_.press); - SetCursor(GetSystemCursor(SystemCursorType::Hand)); - break; - case ClickState::PressInactive: - Set(render_object_.get(), style_.press_cancel); - SetCursor(GetSystemCursor(SystemCursorType::Arrow)); - break; - } - }); -} - -Button::~Button() = default; - -render::RenderObject* Button::GetRenderObject() const { - return render_object_.get(); -} - -void Button::OnChildChanged(Control* old_child, Control* new_child) { - if (old_child != nullptr) render_object_->RemoveChild(0); - if (new_child != nullptr) - render_object_->AddChild(new_child->GetRenderObject(), 0); -} - -} // namespace cru::ui::controls diff --git a/src/ui/controls/container.cpp b/src/ui/controls/container.cpp deleted file mode 100644 index de58ee64..00000000 --- a/src/ui/controls/container.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "cru/ui/controls/Container.hpp" - -#include "cru/platform/graph/Factory.hpp" -#include "cru/ui/render/BorderRenderObject.hpp" - -namespace cru::ui::controls { -Container::Container() { - render_object_ = std::make_unique(); - render_object_->SetBorderEnabled(false); -} - -Container::~Container() = default; - -void Container::OnChildChanged(Control*, Control* new_child) { - render_object_->RemoveChild(0); - render_object_->AddChild(new_child->GetRenderObject(), 0); -} -} // namespace cru::ui::controls diff --git a/src/ui/helper.cpp b/src/ui/helper.cpp deleted file mode 100644 index 6f67e701..00000000 --- a/src/ui/helper.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "Helper.hpp" - -#include "cru/platform/graph/Factory.hpp" -#include "cru/platform/native/UiApplication.hpp" - -namespace cru::ui { -using cru::platform::graph::IGraphFactory; -using cru::platform::native::IUiApplication; - -IGraphFactory* GetGraphFactory() { - return IUiApplication::GetInstance()->GetGraphFactory(); -} - -IUiApplication* GetUiApplication() { return IUiApplication::GetInstance(); } -} // namespace cru::ui diff --git a/src/ui/helper.hpp b/src/ui/helper.hpp deleted file mode 100644 index 6923852f..00000000 --- a/src/ui/helper.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once -#include "cru/ui/Base.hpp" - -namespace cru::platform { -namespace graph { -struct IGraphFactory; -} -namespace native { -struct ICursor; -struct IUiApplication; -} // namespace native -} // namespace cru::platform - -namespace cru::ui { -cru::platform::graph::IGraphFactory* GetGraphFactory(); -cru::platform::native::IUiApplication* GetUiApplication(); -} // namespace cru::ui diff --git a/src/ui/window.cpp b/src/ui/window.cpp deleted file mode 100644 index de7044dd..00000000 --- a/src/ui/window.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "cru/ui/Window.hpp" - -#include "cru/ui/render/WindowRenderObject.hpp" -#include "cru/ui/UiHost.hpp" - -namespace cru::ui { -Window* Window::CreateOverlapped() { - return new Window(tag_overlapped_constructor{}); -} - -Window::Window(tag_overlapped_constructor) { - managed_ui_host_ = std::make_unique(this); -} - -Window::~Window() { - // explicit destroy ui host first. - managed_ui_host_.reset(); -} - -std::string_view Window::GetControlType() const { return control_type; } - -render::RenderObject* Window::GetRenderObject() const { return render_object_; } - -void Window::OnChildChanged(Control* old_child, Control* new_child) { - if (old_child) render_object_->RemoveChild(0); - if (new_child) render_object_->AddChild(new_child->GetRenderObject(), 0); -} -} // namespace cru::ui diff --git a/src/win/Exception.cpp b/src/win/Exception.cpp new file mode 100644 index 00000000..9f9fb03d --- /dev/null +++ b/src/win/Exception.cpp @@ -0,0 +1,38 @@ +#include "cru/win/Exception.hpp" + +#include +#include + +namespace cru::platform::win { + +inline std::string HResultMakeMessage(HRESULT h_result, + std::optional message) { + if (message.has_value()) + return fmt::format(FMT_STRING("HRESULT: {:#08x}. Message: {}"), h_result, + *message); + else + return fmt::format(FMT_STRING("HRESULT: {:#08x}."), h_result); +} + +HResultError::HResultError(HRESULT h_result) + : PlatformException(HResultMakeMessage(h_result, std::nullopt)), + h_result_(h_result) {} + +HResultError::HResultError(HRESULT h_result, + const std::string_view& additional_message) + : PlatformException(HResultMakeMessage(h_result, additional_message)), + h_result_(h_result) {} + +inline std::string Win32MakeMessage(DWORD error_code, + std::string_view message) { + return fmt::format("Last error code: {:#04x}.\nMessage: {}\n", error_code, + message); +} + +Win32Error::Win32Error(const std::string_view& message) + : Win32Error(::GetLastError(), message) {} + +Win32Error::Win32Error(DWORD error_code, const std::string_view& message) + : PlatformException(Win32MakeMessage(error_code, message)), + error_code_(error_code) {} +} // namespace cru::platform::win diff --git a/src/win/String.cpp b/src/win/String.cpp new file mode 100644 index 00000000..65a280f2 --- /dev/null +++ b/src/win/String.cpp @@ -0,0 +1,188 @@ +#include "cru/win/String.hpp" + +#include "cru/win/Exception.hpp" + +#include + +namespace cru::platform::win { +std::string ToUtf8String(const std::wstring_view& string) { + if (string.empty()) return std::string{}; + + const auto length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), + static_cast(string.size()), nullptr, 0, nullptr, nullptr); + if (length == 0) { + throw Win32Error(::GetLastError(), + "Failed to convert wide string to UTF-8."); + } + + std::string result; + result.resize(length); + if (::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), + static_cast(string.size()), result.data(), + static_cast(result.size()), nullptr, + nullptr) == 0) + throw Win32Error(::GetLastError(), + "Failed to convert wide string to UTF-8."); + return result; +} + +std::wstring ToUtf16String(const std::string_view& string) { + if (string.empty()) return std::wstring{}; + + const auto length = + ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, string.data(), + static_cast(string.size()), nullptr, 0); + if (length == 0) { + throw Win32Error(::GetLastError(), + "Failed to convert wide string to UTF-16."); + } + + std::wstring result; + result.resize(length); + if (::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, string.data(), + static_cast(string.size()), result.data(), + static_cast(result.size())) == 0) + throw win::Win32Error(::GetLastError(), + "Failed to convert wide string to UTF-16."); + return result; +} + +template +inline std::enable_if_t, CodePoint> ExtractBits( + UInt n) { + return static_cast(n & ((1u << number_of_bit) - 1)); +} + +CodePoint Utf8Iterator::Next() { + if (position_ == static_cast(string_.length())) return k_code_point_end; + + const auto cu0 = static_cast(string_[position_++]); + + auto read_next_folowing_code = [this]() -> CodePoint { + if (this->position_ == static_cast(string_.length())) + throw TextEncodeException( + "Unexpected end when read continuing byte of multi-byte code point."); + +#ifdef CRU_DEBUG + const auto u = static_cast(string_[position_]); + if (!(u & (1u << 7)) || (u & (1u << 6))) { + throw TextEncodeException( + "Unexpected bad-format (not 0b10xxxxxx) continuing byte of " + "multi-byte code point."); + } +#endif + + return ExtractBits(string_[position_++]); + }; + + if ((1u << 7) & cu0) { + if ((1u << 6) & cu0) { // 2~4-length code point + if ((1u << 5) & cu0) { // 3~4-length code point + if ((1u << 4) & cu0) { // 4-length code point +#ifdef CRU_DEBUG + if (cu0 & (1u << 3)) { + throw TextEncodeException( + "Unexpected bad-format begin byte (not 0b10xxxxxx) of 4-byte " + "code point."); + } +#endif + + const CodePoint s0 = ExtractBits(cu0) << (6 * 3); + const CodePoint s1 = read_next_folowing_code() << (6 * 2); + const CodePoint s2 = read_next_folowing_code() << 6; + const CodePoint s3 = read_next_folowing_code(); + return s0 + s1 + s2 + s3; + } else { // 3-length code point + const CodePoint s0 = ExtractBits(cu0) << (6 * 2); + const CodePoint s1 = read_next_folowing_code() << 6; + const CodePoint s2 = read_next_folowing_code(); + return s0 + s1 + s2; + } + } else { // 2-length code point + const CodePoint s0 = ExtractBits(cu0) << 6; + const CodePoint s1 = read_next_folowing_code(); + return s0 + s1; + } + } else { + throw TextEncodeException( + "Unexpected bad-format (0b10xxxxxx) begin byte of a code point."); + } + } else { + return static_cast(cu0); + } +} + +CodePoint Utf16Iterator::Next() { + if (position_ == static_cast(string_.length())) return k_code_point_end; + + const auto cu0 = static_cast(string_[position_++]); + + if (cu0 < 0xd800u || cu0 >= 0xe000u) { // 1-length code point + return static_cast(cu0); + } else if (cu0 <= 0xdbffu) { // 2-length code point + if (position_ == static_cast(string_.length())) { + throw TextEncodeException( + "Unexpected end when reading second code unit of surrogate pair."); + } + const auto cu1 = static_cast(string_[position_++]); + +#ifdef CRU_DEBUG + if (cu1 < 0xDC00u || cu1 > 0xdfffu) { + throw TextEncodeException( + "Unexpected bad-format second code unit of surrogate pair."); + } +#endif + + const auto s0 = ExtractBits(cu0) << 10; + const auto s1 = ExtractBits(cu1); + + return s0 + s1 + 0x10000; + + } else { + throw TextEncodeException( + "Unexpected bad-format first code unit of surrogate pair."); + } +} + +int IndexUtf8ToUtf16(const std::string_view& utf8_string, int utf8_index, + const std::wstring_view& utf16_string) { + if (utf8_index >= static_cast(utf8_string.length())) + return static_cast(utf16_string.length()); + + int cp_index = 0; + Utf8Iterator iter{utf8_string}; + while (iter.CurrentPosition() <= utf8_index) { + iter.Next(); + cp_index++; + } + + Utf16Iterator result_iter{utf16_string}; + for (int i = 0; i < cp_index - 1; i++) { + if (result_iter.Next() == k_code_point_end) break; + } + + return result_iter.CurrentPosition(); +} + +int IndexUtf16ToUtf8(const std::wstring_view& utf16_string, int utf16_index, + const std::string_view& utf8_string) { + if (utf16_index >= static_cast(utf16_string.length())) + return static_cast(utf8_string.length()); + + int cp_index = 0; + Utf16Iterator iter{utf16_string}; + while (iter.CurrentPosition() <= utf16_index) { + iter.Next(); + cp_index++; + } + + Utf8Iterator result_iter{utf8_string}; + for (int i = 0; i < cp_index - 1; i++) { + if (result_iter.Next() == k_code_point_end) break; + } + + return result_iter.CurrentPosition(); +} + +} // namespace cru::platform::win diff --git a/src/win/exception.cpp b/src/win/exception.cpp deleted file mode 100644 index 9f9fb03d..00000000 --- a/src/win/exception.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "cru/win/Exception.hpp" - -#include -#include - -namespace cru::platform::win { - -inline std::string HResultMakeMessage(HRESULT h_result, - std::optional message) { - if (message.has_value()) - return fmt::format(FMT_STRING("HRESULT: {:#08x}. Message: {}"), h_result, - *message); - else - return fmt::format(FMT_STRING("HRESULT: {:#08x}."), h_result); -} - -HResultError::HResultError(HRESULT h_result) - : PlatformException(HResultMakeMessage(h_result, std::nullopt)), - h_result_(h_result) {} - -HResultError::HResultError(HRESULT h_result, - const std::string_view& additional_message) - : PlatformException(HResultMakeMessage(h_result, additional_message)), - h_result_(h_result) {} - -inline std::string Win32MakeMessage(DWORD error_code, - std::string_view message) { - return fmt::format("Last error code: {:#04x}.\nMessage: {}\n", error_code, - message); -} - -Win32Error::Win32Error(const std::string_view& message) - : Win32Error(::GetLastError(), message) {} - -Win32Error::Win32Error(DWORD error_code, const std::string_view& message) - : PlatformException(Win32MakeMessage(error_code, message)), - error_code_(error_code) {} -} // namespace cru::platform::win diff --git a/src/win/graph/direct/Brush.cpp b/src/win/graph/direct/Brush.cpp new file mode 100644 index 00000000..2b9d4ac3 --- /dev/null +++ b/src/win/graph/direct/Brush.cpp @@ -0,0 +1,17 @@ +#include "cru/win/graph/direct/Brush.hpp" + +#include "cru/win/graph/direct/ConvertUtil.hpp" +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Factory.hpp" + +namespace cru::platform::graph::win::direct { +D2DSolidColorBrush::D2DSolidColorBrush(DirectGraphFactory* factory) + : DirectGraphResource(factory) { + ThrowIfFailed(factory->GetDefaultD2D1DeviceContext()->CreateSolidColorBrush( + Convert(color_), &brush_)); +} + +void D2DSolidColorBrush::SetColor(const Color& color) { + brush_->SetColor(Convert(color)); +} +} // namespace cru::platform::graph::win::direct diff --git a/src/win/graph/direct/Factory.cpp b/src/win/graph/direct/Factory.cpp new file mode 100644 index 00000000..d9213994 --- /dev/null +++ b/src/win/graph/direct/Factory.cpp @@ -0,0 +1,107 @@ +#include "cru/win/graph/direct/Factory.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/win/graph/direct/Brush.hpp" +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Font.hpp" +#include "cru/win/graph/direct/Geometry.hpp" +#include "cru/win/graph/direct/TextLayout.hpp" + +#include +#include + +namespace cru::platform::graph::win::direct { +namespace { +void InitializeCom() { + const auto hresult = ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + if (FAILED(hresult)) { + throw HResultError(hresult, "Failed to call CoInitializeEx."); + } + if (hresult == S_FALSE) { + log::Debug( + "Try to call CoInitializeEx, but it seems COM is already " + "initialized."); + } +} + +void UninitializeCom() { ::CoUninitialize(); } +} // namespace + +DirectGraphFactory::DirectGraphFactory() { + // TODO! Detect repeated creation. Because I don't think we can create two d2d + // and dwrite factory so we need to prevent the "probably dangerous" behavior. + + InitializeCom(); + + UINT creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + +#ifdef CRU_DEBUG + creation_flags |= D3D11_CREATE_DEVICE_DEBUG; +#endif + + const D3D_FEATURE_LEVEL feature_levels[] = { + D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_2, + D3D_FEATURE_LEVEL_9_1}; + + Microsoft::WRL::ComPtr d3d11_device_context; + + ThrowIfFailed(D3D11CreateDevice( + nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, creation_flags, + feature_levels, ARRAYSIZE(feature_levels), D3D11_SDK_VERSION, + &d3d11_device_, nullptr, &d3d11_device_context)); + + Microsoft::WRL::ComPtr dxgi_device; + ThrowIfFailed(d3d11_device_->QueryInterface(dxgi_device.GetAddressOf())); + + ThrowIfFailed(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, + IID_PPV_ARGS(&d2d1_factory_))); + + ThrowIfFailed(d2d1_factory_->CreateDevice(dxgi_device.Get(), &d2d1_device_)); + + d2d1_device_context_ = CreateD2D1DeviceContext(); + + // Identify the physical adapter (GPU or card) this device is runs on. + Microsoft::WRL::ComPtr dxgi_adapter; + ThrowIfFailed(dxgi_device->GetAdapter(&dxgi_adapter)); + + // Get the factory object that created the DXGI device. + ThrowIfFailed(dxgi_adapter->GetParent(IID_PPV_ARGS(&dxgi_factory_))); + + ThrowIfFailed(DWriteCreateFactory( + DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), + reinterpret_cast(dwrite_factory_.GetAddressOf()))); + + ThrowIfFailed(dwrite_factory_->GetSystemFontCollection( + &dwrite_system_font_collection_)); +} + +DirectGraphFactory::~DirectGraphFactory() { UninitializeCom(); } + +Microsoft::WRL::ComPtr +DirectGraphFactory::CreateD2D1DeviceContext() { + Microsoft::WRL::ComPtr d2d1_device_context; + ThrowIfFailed(d2d1_device_->CreateDeviceContext( + D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &d2d1_device_context)); + return d2d1_device_context; +} + +std::unique_ptr DirectGraphFactory::CreateSolidColorBrush() { + return std::make_unique(this); +} + +std::unique_ptr DirectGraphFactory::CreateGeometryBuilder() { + return std::make_unique(this); +} + +std::unique_ptr DirectGraphFactory::CreateFont( + const std::string_view& font_family, float font_size) { + return std::make_unique(this, font_family, font_size); +} + +std::unique_ptr DirectGraphFactory::CreateTextLayout( + std::shared_ptr font, std::string text) { + return std::make_unique(this, std::move(font), + std::move(text)); +} +} // namespace cru::platform::graph::win::direct diff --git a/src/win/graph/direct/Font.cpp b/src/win/graph/direct/Font.cpp new file mode 100644 index 00000000..edbbc59d --- /dev/null +++ b/src/win/graph/direct/Font.cpp @@ -0,0 +1,33 @@ +#include "cru/win/graph/direct/Font.hpp" + +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Factory.hpp" +#include "cru/win/String.hpp" + +#include +#include + +namespace cru::platform::graph::win::direct { +DWriteFont::DWriteFont(DirectGraphFactory* factory, + const std::string_view& font_family, float font_size) + : DirectGraphResource(factory) { + // Get locale + std::array buffer; + if (!::GetUserDefaultLocaleName(buffer.data(), + static_cast(buffer.size()))) + throw platform::win::Win32Error( + ::GetLastError(), "Failed to get locale when create dwrite font."); + + const std::wstring&& wff = cru::platform::win::ToUtf16String(font_family); + + ThrowIfFailed(factory->GetDWriteFactory()->CreateTextFormat( + wff.data(), nullptr, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, + DWRITE_FONT_STRETCH_NORMAL, font_size, buffer.data(), &text_format_)); + + ThrowIfFailed(text_format_->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING)); + ThrowIfFailed( + text_format_->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR)); +} + +float DWriteFont::GetFontSize() { return text_format_->GetFontSize(); } +} // namespace cru::platform::graph::win::direct diff --git a/src/win/graph/direct/Geometry.cpp b/src/win/graph/direct/Geometry.cpp new file mode 100644 index 00000000..e77b4749 --- /dev/null +++ b/src/win/graph/direct/Geometry.cpp @@ -0,0 +1,62 @@ +#include "cru/win/graph/direct/Geometry.hpp" + +#include "cru/win/graph/direct/ConvertUtil.hpp" +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Factory.hpp" + +namespace cru::platform::graph::win::direct { +D2DGeometryBuilder::D2DGeometryBuilder(DirectGraphFactory* factory) + : DirectGraphResource(factory) { + ThrowIfFailed(factory->GetD2D1Factory()->CreatePathGeometry(&geometry_)); + ThrowIfFailed(geometry_->Open(&geometry_sink_)); +} + +void D2DGeometryBuilder::CheckValidation() { + if (!IsValid()) + throw ReuseException("The geometry builder is already disposed."); +} + +void D2DGeometryBuilder::BeginFigure(const Point& point) { + CheckValidation(); + geometry_sink_->BeginFigure(Convert(point), D2D1_FIGURE_BEGIN_FILLED); +} + +void D2DGeometryBuilder::LineTo(const Point& point) { + CheckValidation(); + geometry_sink_->AddLine(Convert(point)); +} + +void D2DGeometryBuilder::QuadraticBezierTo(const Point& control_point, + const Point& end_point) { + CheckValidation(); + geometry_sink_->AddQuadraticBezier( + D2D1::QuadraticBezierSegment(Convert(control_point), Convert(end_point))); +} + +void D2DGeometryBuilder::CloseFigure(bool close) { + CheckValidation(); + geometry_sink_->EndFigure(close ? D2D1_FIGURE_END_CLOSED + : D2D1_FIGURE_END_OPEN); +} + +std::unique_ptr D2DGeometryBuilder::Build() { + CheckValidation(); + ThrowIfFailed(geometry_sink_->Close()); + geometry_sink_ = nullptr; + auto geometry = + std::make_unique(GetDirectFactory(), std::move(geometry_)); + geometry_ = nullptr; + return geometry; +} + +D2DGeometry::D2DGeometry(DirectGraphFactory* factory, + Microsoft::WRL::ComPtr geometry) + : DirectGraphResource(factory), geometry_(std::move(geometry)) {} + +bool D2DGeometry::FillContains(const Point& point) { + BOOL result; + ThrowIfFailed(geometry_->FillContainsPoint( + Convert(point), D2D1::Matrix3x2F::Identity(), &result)); + return result != 0; +} +} // namespace cru::platform::graph::win::direct diff --git a/src/win/graph/direct/Painter.cpp b/src/win/graph/direct/Painter.cpp new file mode 100644 index 00000000..3ffb5208 --- /dev/null +++ b/src/win/graph/direct/Painter.cpp @@ -0,0 +1,104 @@ +#include "cru/win/graph/direct/Painter.hpp" + +#include "cru/platform/Check.hpp" +#include "cru/win/graph/direct/Brush.hpp" +#include "cru/win/graph/direct/ConvertUtil.hpp" +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Geometry.hpp" +#include "cru/win/graph/direct/TextLayout.hpp" + +#include + +namespace cru::platform::graph::win::direct { +D2DPainter::D2DPainter(ID2D1RenderTarget* render_target) { + Expects(render_target); + render_target_ = render_target; +} + +platform::Matrix D2DPainter::GetTransform() { + CheckValidation(); + D2D1_MATRIX_3X2_F m; + render_target_->GetTransform(&m); + return Convert(m); +} + +void D2DPainter::SetTransform(const platform::Matrix& matrix) { + CheckValidation(); + render_target_->SetTransform(Convert(matrix)); +} + +void D2DPainter::Clear(const Color& color) { + CheckValidation(); + render_target_->Clear(Convert(color)); +} + +void D2DPainter::StrokeRectangle(const Rect& rectangle, IBrush* brush, + float width) { + CheckValidation(); + const auto b = CheckPlatform(brush, GetPlatformId()); + render_target_->DrawRectangle(Convert(rectangle), b->GetD2DBrushInterface(), + width); +} + +void D2DPainter::FillRectangle(const Rect& rectangle, IBrush* brush) { + CheckValidation(); + const auto b = CheckPlatform(brush, GetPlatformId()); + render_target_->FillRectangle(Convert(rectangle), b->GetD2DBrushInterface()); +} + +void D2DPainter::StrokeGeometry(IGeometry* geometry, IBrush* brush, + float width) { + CheckValidation(); + const auto g = CheckPlatform(geometry, GetPlatformId()); + const auto b = CheckPlatform(brush, GetPlatformId()); + render_target_->DrawGeometry(g->GetComInterface(), b->GetD2DBrushInterface(), + width); +} + +void D2DPainter::FillGeometry(IGeometry* geometry, IBrush* brush) { + CheckValidation(); + const auto g = CheckPlatform(geometry, GetPlatformId()); + const auto b = CheckPlatform(brush, GetPlatformId()); + render_target_->FillGeometry(g->GetComInterface(), b->GetD2DBrushInterface()); +} + +void D2DPainter::DrawText(const Point& offset, ITextLayout* text_layout, + IBrush* brush) { + CheckValidation(); + const auto t = CheckPlatform(text_layout, GetPlatformId()); + const auto b = CheckPlatform(brush, GetPlatformId()); + render_target_->DrawTextLayout(Convert(offset), t->GetComInterface(), + b->GetD2DBrushInterface()); +} + +void D2DPainter::PushLayer(const Rect& bounds) { + CheckValidation(); + + Microsoft::WRL::ComPtr layer; + ThrowIfFailed(render_target_->CreateLayer(&layer)); + + render_target_->PushLayer(D2D1::LayerParameters(Convert(bounds)), + layer.Get()); + + layers_.push_back(std::move(layer)); +} + +void D2DPainter::PopLayer() { + render_target_->PopLayer(); + layers_.pop_back(); +} + +void D2DPainter::EndDraw() { + if (is_drawing_) { + is_drawing_ = false; + DoEndDraw(); + } +} + +void D2DPainter::CheckValidation() { + if (!is_drawing_) { + throw cru::platform::ReuseException( + "Can't do that on painter after end drawing."); + } +} +} // namespace cru::platform::graph::win::direct diff --git a/src/win/graph/direct/Resource.cpp b/src/win/graph/direct/Resource.cpp new file mode 100644 index 00000000..772bb74b --- /dev/null +++ b/src/win/graph/direct/Resource.cpp @@ -0,0 +1,12 @@ +#include "cru/win/graph/direct/Resource.hpp" + +#include "cru/win/graph/direct/Factory.hpp" + +namespace cru::platform::graph::win::direct { +DirectGraphResource::DirectGraphResource(DirectGraphFactory* factory) + : factory_(factory) { + Expects(factory); +} + +IGraphFactory* DirectGraphResource::GetGraphFactory() { return factory_; } +} // namespace cru::platform::graph::win::direct diff --git a/src/win/graph/direct/brush.cpp b/src/win/graph/direct/brush.cpp deleted file mode 100644 index 2b9d4ac3..00000000 --- a/src/win/graph/direct/brush.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "cru/win/graph/direct/Brush.hpp" - -#include "cru/win/graph/direct/ConvertUtil.hpp" -#include "cru/win/graph/direct/Exception.hpp" -#include "cru/win/graph/direct/Factory.hpp" - -namespace cru::platform::graph::win::direct { -D2DSolidColorBrush::D2DSolidColorBrush(DirectGraphFactory* factory) - : DirectGraphResource(factory) { - ThrowIfFailed(factory->GetDefaultD2D1DeviceContext()->CreateSolidColorBrush( - Convert(color_), &brush_)); -} - -void D2DSolidColorBrush::SetColor(const Color& color) { - brush_->SetColor(Convert(color)); -} -} // namespace cru::platform::graph::win::direct diff --git a/src/win/graph/direct/factory.cpp b/src/win/graph/direct/factory.cpp deleted file mode 100644 index d9213994..00000000 --- a/src/win/graph/direct/factory.cpp +++ /dev/null @@ -1,107 +0,0 @@ -#include "cru/win/graph/direct/Factory.hpp" - -#include "cru/common/Logger.hpp" -#include "cru/win/graph/direct/Brush.hpp" -#include "cru/win/graph/direct/Exception.hpp" -#include "cru/win/graph/direct/Font.hpp" -#include "cru/win/graph/direct/Geometry.hpp" -#include "cru/win/graph/direct/TextLayout.hpp" - -#include -#include - -namespace cru::platform::graph::win::direct { -namespace { -void InitializeCom() { - const auto hresult = ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - if (FAILED(hresult)) { - throw HResultError(hresult, "Failed to call CoInitializeEx."); - } - if (hresult == S_FALSE) { - log::Debug( - "Try to call CoInitializeEx, but it seems COM is already " - "initialized."); - } -} - -void UninitializeCom() { ::CoUninitialize(); } -} // namespace - -DirectGraphFactory::DirectGraphFactory() { - // TODO! Detect repeated creation. Because I don't think we can create two d2d - // and dwrite factory so we need to prevent the "probably dangerous" behavior. - - InitializeCom(); - - UINT creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; - -#ifdef CRU_DEBUG - creation_flags |= D3D11_CREATE_DEVICE_DEBUG; -#endif - - const D3D_FEATURE_LEVEL feature_levels[] = { - D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, - D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_2, - D3D_FEATURE_LEVEL_9_1}; - - Microsoft::WRL::ComPtr d3d11_device_context; - - ThrowIfFailed(D3D11CreateDevice( - nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, creation_flags, - feature_levels, ARRAYSIZE(feature_levels), D3D11_SDK_VERSION, - &d3d11_device_, nullptr, &d3d11_device_context)); - - Microsoft::WRL::ComPtr dxgi_device; - ThrowIfFailed(d3d11_device_->QueryInterface(dxgi_device.GetAddressOf())); - - ThrowIfFailed(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, - IID_PPV_ARGS(&d2d1_factory_))); - - ThrowIfFailed(d2d1_factory_->CreateDevice(dxgi_device.Get(), &d2d1_device_)); - - d2d1_device_context_ = CreateD2D1DeviceContext(); - - // Identify the physical adapter (GPU or card) this device is runs on. - Microsoft::WRL::ComPtr dxgi_adapter; - ThrowIfFailed(dxgi_device->GetAdapter(&dxgi_adapter)); - - // Get the factory object that created the DXGI device. - ThrowIfFailed(dxgi_adapter->GetParent(IID_PPV_ARGS(&dxgi_factory_))); - - ThrowIfFailed(DWriteCreateFactory( - DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), - reinterpret_cast(dwrite_factory_.GetAddressOf()))); - - ThrowIfFailed(dwrite_factory_->GetSystemFontCollection( - &dwrite_system_font_collection_)); -} - -DirectGraphFactory::~DirectGraphFactory() { UninitializeCom(); } - -Microsoft::WRL::ComPtr -DirectGraphFactory::CreateD2D1DeviceContext() { - Microsoft::WRL::ComPtr d2d1_device_context; - ThrowIfFailed(d2d1_device_->CreateDeviceContext( - D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &d2d1_device_context)); - return d2d1_device_context; -} - -std::unique_ptr DirectGraphFactory::CreateSolidColorBrush() { - return std::make_unique(this); -} - -std::unique_ptr DirectGraphFactory::CreateGeometryBuilder() { - return std::make_unique(this); -} - -std::unique_ptr DirectGraphFactory::CreateFont( - const std::string_view& font_family, float font_size) { - return std::make_unique(this, font_family, font_size); -} - -std::unique_ptr DirectGraphFactory::CreateTextLayout( - std::shared_ptr font, std::string text) { - return std::make_unique(this, std::move(font), - std::move(text)); -} -} // namespace cru::platform::graph::win::direct diff --git a/src/win/graph/direct/font.cpp b/src/win/graph/direct/font.cpp deleted file mode 100644 index edbbc59d..00000000 --- a/src/win/graph/direct/font.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "cru/win/graph/direct/Font.hpp" - -#include "cru/win/graph/direct/Exception.hpp" -#include "cru/win/graph/direct/Factory.hpp" -#include "cru/win/String.hpp" - -#include -#include - -namespace cru::platform::graph::win::direct { -DWriteFont::DWriteFont(DirectGraphFactory* factory, - const std::string_view& font_family, float font_size) - : DirectGraphResource(factory) { - // Get locale - std::array buffer; - if (!::GetUserDefaultLocaleName(buffer.data(), - static_cast(buffer.size()))) - throw platform::win::Win32Error( - ::GetLastError(), "Failed to get locale when create dwrite font."); - - const std::wstring&& wff = cru::platform::win::ToUtf16String(font_family); - - ThrowIfFailed(factory->GetDWriteFactory()->CreateTextFormat( - wff.data(), nullptr, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, - DWRITE_FONT_STRETCH_NORMAL, font_size, buffer.data(), &text_format_)); - - ThrowIfFailed(text_format_->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING)); - ThrowIfFailed( - text_format_->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR)); -} - -float DWriteFont::GetFontSize() { return text_format_->GetFontSize(); } -} // namespace cru::platform::graph::win::direct diff --git a/src/win/graph/direct/geometry.cpp b/src/win/graph/direct/geometry.cpp deleted file mode 100644 index e77b4749..00000000 --- a/src/win/graph/direct/geometry.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "cru/win/graph/direct/Geometry.hpp" - -#include "cru/win/graph/direct/ConvertUtil.hpp" -#include "cru/win/graph/direct/Exception.hpp" -#include "cru/win/graph/direct/Factory.hpp" - -namespace cru::platform::graph::win::direct { -D2DGeometryBuilder::D2DGeometryBuilder(DirectGraphFactory* factory) - : DirectGraphResource(factory) { - ThrowIfFailed(factory->GetD2D1Factory()->CreatePathGeometry(&geometry_)); - ThrowIfFailed(geometry_->Open(&geometry_sink_)); -} - -void D2DGeometryBuilder::CheckValidation() { - if (!IsValid()) - throw ReuseException("The geometry builder is already disposed."); -} - -void D2DGeometryBuilder::BeginFigure(const Point& point) { - CheckValidation(); - geometry_sink_->BeginFigure(Convert(point), D2D1_FIGURE_BEGIN_FILLED); -} - -void D2DGeometryBuilder::LineTo(const Point& point) { - CheckValidation(); - geometry_sink_->AddLine(Convert(point)); -} - -void D2DGeometryBuilder::QuadraticBezierTo(const Point& control_point, - const Point& end_point) { - CheckValidation(); - geometry_sink_->AddQuadraticBezier( - D2D1::QuadraticBezierSegment(Convert(control_point), Convert(end_point))); -} - -void D2DGeometryBuilder::CloseFigure(bool close) { - CheckValidation(); - geometry_sink_->EndFigure(close ? D2D1_FIGURE_END_CLOSED - : D2D1_FIGURE_END_OPEN); -} - -std::unique_ptr D2DGeometryBuilder::Build() { - CheckValidation(); - ThrowIfFailed(geometry_sink_->Close()); - geometry_sink_ = nullptr; - auto geometry = - std::make_unique(GetDirectFactory(), std::move(geometry_)); - geometry_ = nullptr; - return geometry; -} - -D2DGeometry::D2DGeometry(DirectGraphFactory* factory, - Microsoft::WRL::ComPtr geometry) - : DirectGraphResource(factory), geometry_(std::move(geometry)) {} - -bool D2DGeometry::FillContains(const Point& point) { - BOOL result; - ThrowIfFailed(geometry_->FillContainsPoint( - Convert(point), D2D1::Matrix3x2F::Identity(), &result)); - return result != 0; -} -} // namespace cru::platform::graph::win::direct diff --git a/src/win/graph/direct/painter.cpp b/src/win/graph/direct/painter.cpp deleted file mode 100644 index 3ffb5208..00000000 --- a/src/win/graph/direct/painter.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "cru/win/graph/direct/Painter.hpp" - -#include "cru/platform/Check.hpp" -#include "cru/win/graph/direct/Brush.hpp" -#include "cru/win/graph/direct/ConvertUtil.hpp" -#include "cru/win/graph/direct/Exception.hpp" -#include "cru/win/graph/direct/Geometry.hpp" -#include "cru/win/graph/direct/TextLayout.hpp" - -#include - -namespace cru::platform::graph::win::direct { -D2DPainter::D2DPainter(ID2D1RenderTarget* render_target) { - Expects(render_target); - render_target_ = render_target; -} - -platform::Matrix D2DPainter::GetTransform() { - CheckValidation(); - D2D1_MATRIX_3X2_F m; - render_target_->GetTransform(&m); - return Convert(m); -} - -void D2DPainter::SetTransform(const platform::Matrix& matrix) { - CheckValidation(); - render_target_->SetTransform(Convert(matrix)); -} - -void D2DPainter::Clear(const Color& color) { - CheckValidation(); - render_target_->Clear(Convert(color)); -} - -void D2DPainter::StrokeRectangle(const Rect& rectangle, IBrush* brush, - float width) { - CheckValidation(); - const auto b = CheckPlatform(brush, GetPlatformId()); - render_target_->DrawRectangle(Convert(rectangle), b->GetD2DBrushInterface(), - width); -} - -void D2DPainter::FillRectangle(const Rect& rectangle, IBrush* brush) { - CheckValidation(); - const auto b = CheckPlatform(brush, GetPlatformId()); - render_target_->FillRectangle(Convert(rectangle), b->GetD2DBrushInterface()); -} - -void D2DPainter::StrokeGeometry(IGeometry* geometry, IBrush* brush, - float width) { - CheckValidation(); - const auto g = CheckPlatform(geometry, GetPlatformId()); - const auto b = CheckPlatform(brush, GetPlatformId()); - render_target_->DrawGeometry(g->GetComInterface(), b->GetD2DBrushInterface(), - width); -} - -void D2DPainter::FillGeometry(IGeometry* geometry, IBrush* brush) { - CheckValidation(); - const auto g = CheckPlatform(geometry, GetPlatformId()); - const auto b = CheckPlatform(brush, GetPlatformId()); - render_target_->FillGeometry(g->GetComInterface(), b->GetD2DBrushInterface()); -} - -void D2DPainter::DrawText(const Point& offset, ITextLayout* text_layout, - IBrush* brush) { - CheckValidation(); - const auto t = CheckPlatform(text_layout, GetPlatformId()); - const auto b = CheckPlatform(brush, GetPlatformId()); - render_target_->DrawTextLayout(Convert(offset), t->GetComInterface(), - b->GetD2DBrushInterface()); -} - -void D2DPainter::PushLayer(const Rect& bounds) { - CheckValidation(); - - Microsoft::WRL::ComPtr layer; - ThrowIfFailed(render_target_->CreateLayer(&layer)); - - render_target_->PushLayer(D2D1::LayerParameters(Convert(bounds)), - layer.Get()); - - layers_.push_back(std::move(layer)); -} - -void D2DPainter::PopLayer() { - render_target_->PopLayer(); - layers_.pop_back(); -} - -void D2DPainter::EndDraw() { - if (is_drawing_) { - is_drawing_ = false; - DoEndDraw(); - } -} - -void D2DPainter::CheckValidation() { - if (!is_drawing_) { - throw cru::platform::ReuseException( - "Can't do that on painter after end drawing."); - } -} -} // namespace cru::platform::graph::win::direct diff --git a/src/win/graph/direct/resource.cpp b/src/win/graph/direct/resource.cpp deleted file mode 100644 index 772bb74b..00000000 --- a/src/win/graph/direct/resource.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "cru/win/graph/direct/Resource.hpp" - -#include "cru/win/graph/direct/Factory.hpp" - -namespace cru::platform::graph::win::direct { -DirectGraphResource::DirectGraphResource(DirectGraphFactory* factory) - : factory_(factory) { - Expects(factory); -} - -IGraphFactory* DirectGraphResource::GetGraphFactory() { return factory_; } -} // namespace cru::platform::graph::win::direct diff --git a/src/win/native/Cursor.cpp b/src/win/native/Cursor.cpp new file mode 100644 index 00000000..ca8bb1cd --- /dev/null +++ b/src/win/native/Cursor.cpp @@ -0,0 +1,51 @@ +#include "cru/win/native/Cursor.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/win/native/Exception.hpp" + +#include + +namespace cru::platform::native::win { +WinCursor::WinCursor(HCURSOR handle, bool auto_destroy) { + handle_ = handle; + auto_destroy_ = auto_destroy; +} + +WinCursor::~WinCursor() { + if (auto_destroy_) { + if (!::DestroyCursor(handle_)) { + // This is not a fetal error but might still need notice because it may + // cause leak. + log::Warn("Failed to destroy a cursor. Last error code: {}", + ::GetLastError()); + } + } +} + +namespace { +WinCursor* LoadWinCursor(const wchar_t* name) { + const auto handle = static_cast(::LoadImageW( + NULL, name, IMAGE_CURSOR, SM_CYCURSOR, SM_CYCURSOR, LR_SHARED)); + if (handle == NULL) { + throw Win32Error(::GetLastError(), "Failed to load system cursor."); + } + return new WinCursor(handle, false); +} +} // namespace + +WinCursorManager::WinCursorManager() + : sys_arrow_(LoadWinCursor(IDC_ARROW)), + sys_hand_(LoadWinCursor(IDC_HAND)) {} + +std::shared_ptr WinCursorManager::GetSystemWinCursor( + SystemCursorType type) { + switch (type) { + case SystemCursorType::Arrow: + return sys_arrow_; + case SystemCursorType::Hand: + return sys_hand_; + default: + throw std::runtime_error("Unknown system cursor value."); + } +} +} // namespace cru::platform::native::win diff --git a/src/win/native/Keyboard.cpp b/src/win/native/Keyboard.cpp new file mode 100644 index 00000000..aa22e4a4 --- /dev/null +++ b/src/win/native/Keyboard.cpp @@ -0,0 +1,74 @@ +#include "cru/win/native/Keyboard.hpp" + +namespace cru::platform::native::win { +KeyCode VirtualKeyToKeyCode(int virtual_key) { + if (virtual_key >= 0x30 && virtual_key <= 0x39) { + return KeyCode{static_cast(KeyCode::N0) + (virtual_key - 0x30)}; + } else if (virtual_key >= 0x41 && virtual_key <= 0x5a) { + return KeyCode{static_cast(KeyCode::A) + (virtual_key - 0x41)}; + } else if (virtual_key >= VK_NUMPAD0 && virtual_key <= VK_NUMPAD9) { + return KeyCode{static_cast(KeyCode::NumPad0) + + (virtual_key - VK_NUMPAD0)}; + } else if (virtual_key >= VK_F1 && virtual_key <= VK_F12) { + return KeyCode{static_cast(KeyCode::F1) + (virtual_key - VK_F1)}; + } else { + switch (virtual_key) { +#define CRU_MAP_KEY(virtual_key, keycode) \ + case virtual_key: \ + return KeyCode::keycode; + + CRU_MAP_KEY(VK_LBUTTON, LeftButton) + CRU_MAP_KEY(VK_MBUTTON, MiddleButton) + CRU_MAP_KEY(VK_RBUTTON, RightButton) + CRU_MAP_KEY(VK_ESCAPE, Escape) + CRU_MAP_KEY(VK_OEM_3, GraveAccent) + CRU_MAP_KEY(VK_TAB, Tab) + CRU_MAP_KEY(VK_CAPITAL, CapsLock) + CRU_MAP_KEY(VK_LSHIFT, LeftShift) + CRU_MAP_KEY(VK_LCONTROL, LeftCtrl) + CRU_MAP_KEY(VK_LWIN, LeftSuper) + CRU_MAP_KEY(VK_LMENU, LeftAlt) + CRU_MAP_KEY(VK_OEM_MINUS, Minus) + CRU_MAP_KEY(VK_OEM_PLUS, Equal) + CRU_MAP_KEY(VK_BACK, Backspace) + CRU_MAP_KEY(VK_OEM_4, LeftSquareBracket) + CRU_MAP_KEY(VK_OEM_6, RightSquareBracket) + CRU_MAP_KEY(VK_OEM_5, BackSlash) + CRU_MAP_KEY(VK_OEM_1, Semicolon) + CRU_MAP_KEY(VK_OEM_7, Quote) + CRU_MAP_KEY(VK_OEM_COMMA, Comma) + CRU_MAP_KEY(VK_OEM_PERIOD, Period) + CRU_MAP_KEY(VK_OEM_2, Slash) + CRU_MAP_KEY(VK_RSHIFT, RightShift) + CRU_MAP_KEY(VK_RCONTROL, RightCtrl) + CRU_MAP_KEY(VK_RWIN, RightSuper) + CRU_MAP_KEY(VK_RMENU, RightAlt) + CRU_MAP_KEY(VK_INSERT, Insert) + CRU_MAP_KEY(VK_DELETE, Delete) + CRU_MAP_KEY(VK_HOME, Home) + CRU_MAP_KEY(VK_END, End) + CRU_MAP_KEY(VK_PRIOR, PageUp) + CRU_MAP_KEY(VK_NEXT, PageDown) + CRU_MAP_KEY(VK_UP, Up) + CRU_MAP_KEY(VK_LEFT, Left) + CRU_MAP_KEY(VK_DOWN, Down) + CRU_MAP_KEY(VK_RIGHT, Right) + CRU_MAP_KEY(VK_SNAPSHOT, PrintScreen) + CRU_MAP_KEY(VK_PAUSE, Pause) + +#undef CRU_MAP_KEY + + default: + return KeyCode::Unknown; + } + } +} + +KeyModifier RetrieveKeyMofifier() { + KeyModifier result{0}; + if (::GetKeyState(VK_SHIFT) < 0) result |= key_modifiers::shift; + if (::GetKeyState(VK_CONTROL) < 0) result |= key_modifiers::ctrl; + if (::GetKeyState(VK_MENU) < 0) result |= key_modifiers::alt; + return result; +} +} // namespace cru::platform::native::win diff --git a/src/win/native/Timer.cpp b/src/win/native/Timer.cpp new file mode 100644 index 00000000..662067fb --- /dev/null +++ b/src/win/native/Timer.cpp @@ -0,0 +1,28 @@ +#include "Timer.hpp" + +namespace cru::platform::native::win { +TimerManager::TimerManager(GodWindow* god_window) { god_window_ = god_window; } + +UINT_PTR TimerManager::CreateTimer(const UINT milliseconds, const bool loop, + TimerAction action) { + const auto id = current_count_++; + ::SetTimer(god_window_->GetHandle(), id, milliseconds, nullptr); + map_.emplace(id, std::make_pair(loop, std::move(action))); + return id; +} + +void TimerManager::KillTimer(const UINT_PTR id) { + const auto find_result = map_.find(id); + if (find_result != map_.cend()) { + ::KillTimer(god_window_->GetHandle(), id); + map_.erase(find_result); + } +} + +std::optional> TimerManager::GetAction( + const UINT_PTR id) { + const auto find_result = map_.find(id); + if (find_result == map_.cend()) return std::nullopt; + return find_result->second; +} +} // namespace cru::platform::native::win diff --git a/src/win/native/Timer.hpp b/src/win/native/Timer.hpp new file mode 100644 index 00000000..95f186a1 --- /dev/null +++ b/src/win/native/Timer.hpp @@ -0,0 +1,34 @@ +#pragma once +#include "cru/win/WinPreConfig.hpp" + +#include "cru/common/Base.hpp" +#include "cru/win/native/GodWindow.hpp" + +#include +#include +#include +#include + +namespace cru::platform::native::win { +using TimerAction = std::function; + +class TimerManager : public Object { + public: + TimerManager(GodWindow* god_window); + + CRU_DELETE_COPY(TimerManager) + CRU_DELETE_MOVE(TimerManager) + + ~TimerManager() override = default; + + UINT_PTR CreateTimer(UINT milliseconds, bool loop, TimerAction action); + void KillTimer(UINT_PTR id); + std::optional> GetAction(UINT_PTR id); + + private: + GodWindow* god_window_; + + std::map> map_{}; + UINT_PTR current_count_ = 0; +}; +} // namespace cru::platform::native::win diff --git a/src/win/native/Window.cpp b/src/win/native/Window.cpp new file mode 100644 index 00000000..9dde1af3 --- /dev/null +++ b/src/win/native/Window.cpp @@ -0,0 +1,441 @@ +#include "cru/win/native/Window.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/platform/Check.hpp" +#include "cru/win/native/Cursor.hpp" +#include "cru/win/native/Exception.hpp" +#include "cru/win/native/Keyboard.hpp" +#include "cru/win/native/UiApplication.hpp" +#include "cru/win/native/WindowClass.hpp" +#include "cru/win/native/WindowRenderTarget.hpp" +#include "cru/win/String.hpp" +#include "DpiUtil.hpp" +#include "WindowD2DPainter.hpp" +#include "WindowManager.hpp" + +#include +#include + +namespace cru::platform::native::win { +WinNativeWindow::WinNativeWindow(WinUiApplication* application, + WindowClass* window_class, DWORD window_style, + WinNativeWindow* parent) + : application_(application), + resolver_(std::make_shared(this)), + parent_window_(parent) { + Expects(application); // application can't be null. + + if (parent != nullptr) { + throw new std::runtime_error("Can't use a invalid window as parent."); + } + + const auto window_manager = application->GetWindowManager(); + + hwnd_ = CreateWindowExW( + 0, window_class->GetName(), L"", window_style, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + parent == nullptr ? nullptr : parent->GetWindowHandle(), nullptr, + application->GetInstanceHandle(), nullptr); + + if (hwnd_ == nullptr) + throw Win32Error(::GetLastError(), "Failed to create window."); + + window_manager->RegisterWindow(hwnd_, this); + + SetCursor(application->GetCursorManager()->GetSystemCursor( + cru::platform::native::SystemCursorType::Arrow)); + + window_render_target_ = std::make_unique( + application->GetDirectFactory(), hwnd_); +} + +WinNativeWindow::~WinNativeWindow() { + if (!sync_flag_) { + sync_flag_ = true; + Close(); + } + resolver_->Reset(); +} + +void WinNativeWindow::Close() { ::DestroyWindow(hwnd_); } + +bool WinNativeWindow::IsVisible() { return ::IsWindowVisible(hwnd_); } + +void WinNativeWindow::SetVisible(bool is_visible) { + is_visible ? ShowWindow(hwnd_, SW_SHOWNORMAL) : ShowWindow(hwnd_, SW_HIDE); +} +Size WinNativeWindow::GetClientSize() { + const auto pixel_rect = GetClientRectPixel(); + return Size(PixelToDipX(pixel_rect.right), PixelToDipY(pixel_rect.bottom)); +} + +void WinNativeWindow::SetClientSize(const Size& size) { + const auto window_style = + static_cast(GetWindowLongPtr(hwnd_, GWL_STYLE)); + const auto window_ex_style = + static_cast(GetWindowLongPtr(hwnd_, GWL_EXSTYLE)); + + RECT rect; + rect.left = 0; + rect.top = 0; + rect.right = DipToPixelX(size.width); + rect.bottom = DipToPixelY(size.height); + if (!AdjustWindowRectEx(&rect, window_style, FALSE, window_ex_style)) + throw Win32Error(::GetLastError(), "Failed to invoke AdjustWindowRectEx."); + + if (!SetWindowPos(hwnd_, nullptr, 0, 0, rect.right - rect.left, + rect.bottom - rect.top, SWP_NOZORDER | SWP_NOMOVE)) + throw Win32Error(::GetLastError(), "Failed to invoke SetWindowPos."); +} + +Rect WinNativeWindow::GetWindowRect() { + RECT rect; + if (!::GetWindowRect(hwnd_, &rect)) + throw Win32Error(::GetLastError(), "Failed to invoke GetWindowRect."); + + return Rect::FromVertices(PixelToDipX(rect.left), PixelToDipY(rect.top), + PixelToDipX(rect.right), PixelToDipY(rect.bottom)); +} + +void WinNativeWindow::SetWindowRect(const Rect& rect) { + if (!SetWindowPos(hwnd_, nullptr, DipToPixelX(rect.left), + DipToPixelY(rect.top), DipToPixelX(rect.GetRight()), + DipToPixelY(rect.GetBottom()), SWP_NOZORDER)) + throw Win32Error(::GetLastError(), "Failed to invoke SetWindowPos."); +} + +Point WinNativeWindow::GetMousePosition() { + POINT p; + if (!::GetCursorPos(&p)) + throw Win32Error(::GetLastError(), "Failed to get cursor position."); + if (!::ScreenToClient(hwnd_, &p)) + throw Win32Error(::GetLastError(), "Failed to call ScreenToClient."); + return PiToDip(p); +} + +bool WinNativeWindow::CaptureMouse() { + ::SetCapture(hwnd_); + return true; +} + +bool WinNativeWindow::ReleaseMouse() { + const auto result = ::ReleaseCapture(); + return result != 0; +} + +void WinNativeWindow::RequestRepaint() { + log::Debug("A repaint is requested."); + if (!::InvalidateRect(hwnd_, nullptr, FALSE)) + throw Win32Error(::GetLastError(), "Failed to invalidate window."); + if (!::UpdateWindow(hwnd_)) + throw Win32Error(::GetLastError(), "Failed to update window."); +} + +std::unique_ptr WinNativeWindow::BeginPaint() { + return std::make_unique(window_render_target_.get()); +} + +void WinNativeWindow::SetCursor(std::shared_ptr cursor) { + if (cursor == nullptr) { + throw std::runtime_error("Can't use a nullptr as cursor."); + } + + cursor_ = CheckPlatform(cursor, GetPlatformId()); + + if (!::SetClassLongPtrW(hwnd_, GCLP_HCURSOR, + reinterpret_cast(cursor_->GetHandle()))) { + log::Warn( + "Failed to set cursor because failed to set class long. Last error " + "code: {}.", + ::GetLastError()); + return; + } + + if (!IsVisible()) return; + + auto lg = [](const std::string_view& reason) { + log::Warn( + "Failed to set cursor because {} when window is visible. (We need " + "to update cursor if it is inside the window.) Last error code: {}.", + reason, ::GetLastError()); + }; + + ::POINT point; + if (!::GetCursorPos(&point)) { + lg("failed to get cursor pos"); + return; + } + + ::RECT rect; + if (!::GetClientRect(hwnd_, &rect)) { + lg("failed to get window's client rect"); + return; + } + + ::POINT lefttop{rect.left, rect.top}; + ::POINT rightbottom{rect.right, rect.bottom}; + if (!::ClientToScreen(hwnd_, &lefttop)) { + lg("failed to call ClientToScreen on lefttop"); + return; + } + + if (!::ClientToScreen(hwnd_, &rightbottom)) { + lg("failed to call ClientToScreen on rightbottom"); + return; + } + + if (point.x >= lefttop.x && point.y >= lefttop.y && + point.x <= rightbottom.x && point.y <= rightbottom.y) { + ::SetCursor(cursor_->GetHandle()); + } +} + +bool WinNativeWindow::HandleNativeWindowMessage(HWND hwnd, UINT msg, + WPARAM w_param, LPARAM l_param, + LRESULT* result) { + WindowNativeMessageEventArgs args{ + WindowNativeMessage{hwnd, msg, w_param, l_param}}; + native_message_event_.Raise(args); + if (args.IsHandled()) { + *result = args.GetResult(); + return true; + } + + switch (msg) { + case WM_PAINT: + OnPaintInternal(); + *result = 0; + return true; + case WM_ERASEBKGND: + *result = 1; + return true; + case WM_SETFOCUS: + OnSetFocusInternal(); + *result = 0; + return true; + case WM_KILLFOCUS: + OnKillFocusInternal(); + *result = 0; + return true; + case WM_MOUSEMOVE: { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseMoveInternal(point); + *result = 0; + return true; + } + case WM_LBUTTONDOWN: { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseDownInternal(platform::native::mouse_buttons::left, point); + *result = 0; + return true; + } + case WM_LBUTTONUP: { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseUpInternal(platform::native::mouse_buttons::left, point); + *result = 0; + return true; + } + case WM_RBUTTONDOWN: { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseDownInternal(platform::native::mouse_buttons::right, point); + *result = 0; + return true; + } + case WM_RBUTTONUP: { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseUpInternal(platform::native::mouse_buttons::right, point); + *result = 0; + return true; + } + case WM_MBUTTONDOWN: { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseDownInternal(platform::native::mouse_buttons::middle, point); + *result = 0; + return true; + } + case WM_MBUTTONUP: { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseUpInternal(platform::native::mouse_buttons::middle, point); + *result = 0; + return true; + } + case WM_MOUSEWHEEL: + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + ScreenToClient(hwnd, &point); + OnMouseWheelInternal(GET_WHEEL_DELTA_WPARAM(w_param), point); + *result = 0; + return true; + case WM_KEYDOWN: + OnKeyDownInternal(static_cast(w_param)); + *result = 0; + return true; + case WM_KEYUP: + OnKeyUpInternal(static_cast(w_param)); + *result = 0; + return true; + case WM_SYSKEYDOWN: + if (l_param & (1 << 29)) { + OnKeyDownInternal(static_cast(w_param)); + *result = 0; + return true; + } + return false; + case WM_SYSKEYUP: + if (l_param & (1 << 29)) { + OnKeyUpInternal(static_cast(w_param)); + *result = 0; + return true; + } + return false; + case WM_SIZE: + OnResizeInternal(LOWORD(l_param), HIWORD(l_param)); + *result = 0; + return true; + case WM_ACTIVATE: + if (w_param == WA_ACTIVE || w_param == WA_CLICKACTIVE) + OnActivatedInternal(); + else if (w_param == WA_INACTIVE) + OnDeactivatedInternal(); + *result = 0; + return true; + case WM_DESTROY: + OnDestroyInternal(); + *result = 0; + return true; + case WM_IME_SETCONTEXT: + l_param &= ~ISC_SHOWUICOMPOSITIONWINDOW; + *result = ::DefWindowProcW(hwnd, msg, w_param, l_param); + return true; + // We must block these message from DefWindowProc or it will create + // an ugly composition window. + case WM_IME_STARTCOMPOSITION: + case WM_IME_COMPOSITION: + *result = 0; + return true; + default: + return false; + } +} + +RECT WinNativeWindow::GetClientRectPixel() { + RECT rect; + if (!GetClientRect(hwnd_, &rect)) + throw Win32Error(::GetLastError(), "Failed to invoke GetClientRect."); + return rect; +} + +void WinNativeWindow::OnDestroyInternal() { + application_->GetWindowManager()->UnregisterWindow(hwnd_); + hwnd_ = nullptr; + destroy_event_.Raise(nullptr); + if (!sync_flag_) { + sync_flag_ = true; + delete this; + } +} + +void WinNativeWindow::OnPaintInternal() { + paint_event_.Raise(nullptr); + ValidateRect(hwnd_, nullptr); +} + +void WinNativeWindow::OnResizeInternal(const int new_width, + const int new_height) { + if (!(new_width == 0 && new_height == 0)) { + window_render_target_->ResizeBuffer(new_width, new_height); + resize_event_.Raise(Size{PixelToDipX(new_width), PixelToDipY(new_height)}); + } +} + +void WinNativeWindow::OnSetFocusInternal() { + has_focus_ = true; + focus_event_.Raise(FocusChangeType::Gain); +} + +void WinNativeWindow::OnKillFocusInternal() { + has_focus_ = false; + focus_event_.Raise(FocusChangeType::Lost); +} + +void WinNativeWindow::OnMouseMoveInternal(const POINT point) { + // when mouse was previous outside the window + if (!is_mouse_in_) { + // invoke TrackMouseEvent to have WM_MOUSELEAVE sent. + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof tme; + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = hwnd_; + + TrackMouseEvent(&tme); + + is_mouse_in_ = true; + mouse_enter_leave_event_.Raise(MouseEnterLeaveType::Enter); + } + + mouse_move_event_.Raise(PiToDip(point)); +} + +void WinNativeWindow::OnMouseLeaveInternal() { + is_mouse_in_ = false; + mouse_enter_leave_event_.Raise(MouseEnterLeaveType::Leave); +} + +void WinNativeWindow::OnMouseDownInternal(platform::native::MouseButton button, + POINT point) { + const auto dip_point = PiToDip(point); + mouse_down_event_.Raise({button, dip_point, RetrieveKeyMofifier()}); +} + +void WinNativeWindow::OnMouseUpInternal(platform::native::MouseButton button, + POINT point) { + const auto dip_point = PiToDip(point); + mouse_up_event_.Raise({button, dip_point, RetrieveKeyMofifier()}); +} + +void WinNativeWindow::OnMouseWheelInternal(short delta, POINT point) { + CRU_UNUSED(delta) + CRU_UNUSED(point) +} + +void WinNativeWindow::OnKeyDownInternal(int virtual_code) { + key_down_event_.Raise( + {VirtualKeyToKeyCode(virtual_code), RetrieveKeyMofifier()}); +} + +void WinNativeWindow::OnKeyUpInternal(int virtual_code) { + key_up_event_.Raise( + {VirtualKeyToKeyCode(virtual_code), RetrieveKeyMofifier()}); +} + +void WinNativeWindow::OnActivatedInternal() {} + +void WinNativeWindow::OnDeactivatedInternal() {} + +void WinNativeWindowResolver::Reset() { + Expects(window_); // already reset, can't reset again + window_ = nullptr; +} + +WinNativeWindow* Resolve(gsl::not_null resolver) { + const auto window = resolver->Resolve(); + return window == nullptr ? nullptr + : CheckPlatform( + window, WinNativeResource::k_platform_id); +} // namespace cru::platform::native::win +} // namespace cru::platform::native::win diff --git a/src/win/native/cursor.cpp b/src/win/native/cursor.cpp deleted file mode 100644 index ca8bb1cd..00000000 --- a/src/win/native/cursor.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "cru/win/native/Cursor.hpp" - -#include "cru/common/Logger.hpp" -#include "cru/win/native/Exception.hpp" - -#include - -namespace cru::platform::native::win { -WinCursor::WinCursor(HCURSOR handle, bool auto_destroy) { - handle_ = handle; - auto_destroy_ = auto_destroy; -} - -WinCursor::~WinCursor() { - if (auto_destroy_) { - if (!::DestroyCursor(handle_)) { - // This is not a fetal error but might still need notice because it may - // cause leak. - log::Warn("Failed to destroy a cursor. Last error code: {}", - ::GetLastError()); - } - } -} - -namespace { -WinCursor* LoadWinCursor(const wchar_t* name) { - const auto handle = static_cast(::LoadImageW( - NULL, name, IMAGE_CURSOR, SM_CYCURSOR, SM_CYCURSOR, LR_SHARED)); - if (handle == NULL) { - throw Win32Error(::GetLastError(), "Failed to load system cursor."); - } - return new WinCursor(handle, false); -} -} // namespace - -WinCursorManager::WinCursorManager() - : sys_arrow_(LoadWinCursor(IDC_ARROW)), - sys_hand_(LoadWinCursor(IDC_HAND)) {} - -std::shared_ptr WinCursorManager::GetSystemWinCursor( - SystemCursorType type) { - switch (type) { - case SystemCursorType::Arrow: - return sys_arrow_; - case SystemCursorType::Hand: - return sys_hand_; - default: - throw std::runtime_error("Unknown system cursor value."); - } -} -} // namespace cru::platform::native::win diff --git a/src/win/native/keyboard.cpp b/src/win/native/keyboard.cpp deleted file mode 100644 index aa22e4a4..00000000 --- a/src/win/native/keyboard.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include "cru/win/native/Keyboard.hpp" - -namespace cru::platform::native::win { -KeyCode VirtualKeyToKeyCode(int virtual_key) { - if (virtual_key >= 0x30 && virtual_key <= 0x39) { - return KeyCode{static_cast(KeyCode::N0) + (virtual_key - 0x30)}; - } else if (virtual_key >= 0x41 && virtual_key <= 0x5a) { - return KeyCode{static_cast(KeyCode::A) + (virtual_key - 0x41)}; - } else if (virtual_key >= VK_NUMPAD0 && virtual_key <= VK_NUMPAD9) { - return KeyCode{static_cast(KeyCode::NumPad0) + - (virtual_key - VK_NUMPAD0)}; - } else if (virtual_key >= VK_F1 && virtual_key <= VK_F12) { - return KeyCode{static_cast(KeyCode::F1) + (virtual_key - VK_F1)}; - } else { - switch (virtual_key) { -#define CRU_MAP_KEY(virtual_key, keycode) \ - case virtual_key: \ - return KeyCode::keycode; - - CRU_MAP_KEY(VK_LBUTTON, LeftButton) - CRU_MAP_KEY(VK_MBUTTON, MiddleButton) - CRU_MAP_KEY(VK_RBUTTON, RightButton) - CRU_MAP_KEY(VK_ESCAPE, Escape) - CRU_MAP_KEY(VK_OEM_3, GraveAccent) - CRU_MAP_KEY(VK_TAB, Tab) - CRU_MAP_KEY(VK_CAPITAL, CapsLock) - CRU_MAP_KEY(VK_LSHIFT, LeftShift) - CRU_MAP_KEY(VK_LCONTROL, LeftCtrl) - CRU_MAP_KEY(VK_LWIN, LeftSuper) - CRU_MAP_KEY(VK_LMENU, LeftAlt) - CRU_MAP_KEY(VK_OEM_MINUS, Minus) - CRU_MAP_KEY(VK_OEM_PLUS, Equal) - CRU_MAP_KEY(VK_BACK, Backspace) - CRU_MAP_KEY(VK_OEM_4, LeftSquareBracket) - CRU_MAP_KEY(VK_OEM_6, RightSquareBracket) - CRU_MAP_KEY(VK_OEM_5, BackSlash) - CRU_MAP_KEY(VK_OEM_1, Semicolon) - CRU_MAP_KEY(VK_OEM_7, Quote) - CRU_MAP_KEY(VK_OEM_COMMA, Comma) - CRU_MAP_KEY(VK_OEM_PERIOD, Period) - CRU_MAP_KEY(VK_OEM_2, Slash) - CRU_MAP_KEY(VK_RSHIFT, RightShift) - CRU_MAP_KEY(VK_RCONTROL, RightCtrl) - CRU_MAP_KEY(VK_RWIN, RightSuper) - CRU_MAP_KEY(VK_RMENU, RightAlt) - CRU_MAP_KEY(VK_INSERT, Insert) - CRU_MAP_KEY(VK_DELETE, Delete) - CRU_MAP_KEY(VK_HOME, Home) - CRU_MAP_KEY(VK_END, End) - CRU_MAP_KEY(VK_PRIOR, PageUp) - CRU_MAP_KEY(VK_NEXT, PageDown) - CRU_MAP_KEY(VK_UP, Up) - CRU_MAP_KEY(VK_LEFT, Left) - CRU_MAP_KEY(VK_DOWN, Down) - CRU_MAP_KEY(VK_RIGHT, Right) - CRU_MAP_KEY(VK_SNAPSHOT, PrintScreen) - CRU_MAP_KEY(VK_PAUSE, Pause) - -#undef CRU_MAP_KEY - - default: - return KeyCode::Unknown; - } - } -} - -KeyModifier RetrieveKeyMofifier() { - KeyModifier result{0}; - if (::GetKeyState(VK_SHIFT) < 0) result |= key_modifiers::shift; - if (::GetKeyState(VK_CONTROL) < 0) result |= key_modifiers::ctrl; - if (::GetKeyState(VK_MENU) < 0) result |= key_modifiers::alt; - return result; -} -} // namespace cru::platform::native::win diff --git a/src/win/native/timer.cpp b/src/win/native/timer.cpp deleted file mode 100644 index 662067fb..00000000 --- a/src/win/native/timer.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "Timer.hpp" - -namespace cru::platform::native::win { -TimerManager::TimerManager(GodWindow* god_window) { god_window_ = god_window; } - -UINT_PTR TimerManager::CreateTimer(const UINT milliseconds, const bool loop, - TimerAction action) { - const auto id = current_count_++; - ::SetTimer(god_window_->GetHandle(), id, milliseconds, nullptr); - map_.emplace(id, std::make_pair(loop, std::move(action))); - return id; -} - -void TimerManager::KillTimer(const UINT_PTR id) { - const auto find_result = map_.find(id); - if (find_result != map_.cend()) { - ::KillTimer(god_window_->GetHandle(), id); - map_.erase(find_result); - } -} - -std::optional> TimerManager::GetAction( - const UINT_PTR id) { - const auto find_result = map_.find(id); - if (find_result == map_.cend()) return std::nullopt; - return find_result->second; -} -} // namespace cru::platform::native::win diff --git a/src/win/native/timer.hpp b/src/win/native/timer.hpp deleted file mode 100644 index 95f186a1..00000000 --- a/src/win/native/timer.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once -#include "cru/win/WinPreConfig.hpp" - -#include "cru/common/Base.hpp" -#include "cru/win/native/GodWindow.hpp" - -#include -#include -#include -#include - -namespace cru::platform::native::win { -using TimerAction = std::function; - -class TimerManager : public Object { - public: - TimerManager(GodWindow* god_window); - - CRU_DELETE_COPY(TimerManager) - CRU_DELETE_MOVE(TimerManager) - - ~TimerManager() override = default; - - UINT_PTR CreateTimer(UINT milliseconds, bool loop, TimerAction action); - void KillTimer(UINT_PTR id); - std::optional> GetAction(UINT_PTR id); - - private: - GodWindow* god_window_; - - std::map> map_{}; - UINT_PTR current_count_ = 0; -}; -} // namespace cru::platform::native::win diff --git a/src/win/native/window.cpp b/src/win/native/window.cpp deleted file mode 100644 index 9dde1af3..00000000 --- a/src/win/native/window.cpp +++ /dev/null @@ -1,441 +0,0 @@ -#include "cru/win/native/Window.hpp" - -#include "cru/common/Logger.hpp" -#include "cru/platform/Check.hpp" -#include "cru/win/native/Cursor.hpp" -#include "cru/win/native/Exception.hpp" -#include "cru/win/native/Keyboard.hpp" -#include "cru/win/native/UiApplication.hpp" -#include "cru/win/native/WindowClass.hpp" -#include "cru/win/native/WindowRenderTarget.hpp" -#include "cru/win/String.hpp" -#include "DpiUtil.hpp" -#include "WindowD2DPainter.hpp" -#include "WindowManager.hpp" - -#include -#include - -namespace cru::platform::native::win { -WinNativeWindow::WinNativeWindow(WinUiApplication* application, - WindowClass* window_class, DWORD window_style, - WinNativeWindow* parent) - : application_(application), - resolver_(std::make_shared(this)), - parent_window_(parent) { - Expects(application); // application can't be null. - - if (parent != nullptr) { - throw new std::runtime_error("Can't use a invalid window as parent."); - } - - const auto window_manager = application->GetWindowManager(); - - hwnd_ = CreateWindowExW( - 0, window_class->GetName(), L"", window_style, CW_USEDEFAULT, - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - parent == nullptr ? nullptr : parent->GetWindowHandle(), nullptr, - application->GetInstanceHandle(), nullptr); - - if (hwnd_ == nullptr) - throw Win32Error(::GetLastError(), "Failed to create window."); - - window_manager->RegisterWindow(hwnd_, this); - - SetCursor(application->GetCursorManager()->GetSystemCursor( - cru::platform::native::SystemCursorType::Arrow)); - - window_render_target_ = std::make_unique( - application->GetDirectFactory(), hwnd_); -} - -WinNativeWindow::~WinNativeWindow() { - if (!sync_flag_) { - sync_flag_ = true; - Close(); - } - resolver_->Reset(); -} - -void WinNativeWindow::Close() { ::DestroyWindow(hwnd_); } - -bool WinNativeWindow::IsVisible() { return ::IsWindowVisible(hwnd_); } - -void WinNativeWindow::SetVisible(bool is_visible) { - is_visible ? ShowWindow(hwnd_, SW_SHOWNORMAL) : ShowWindow(hwnd_, SW_HIDE); -} -Size WinNativeWindow::GetClientSize() { - const auto pixel_rect = GetClientRectPixel(); - return Size(PixelToDipX(pixel_rect.right), PixelToDipY(pixel_rect.bottom)); -} - -void WinNativeWindow::SetClientSize(const Size& size) { - const auto window_style = - static_cast(GetWindowLongPtr(hwnd_, GWL_STYLE)); - const auto window_ex_style = - static_cast(GetWindowLongPtr(hwnd_, GWL_EXSTYLE)); - - RECT rect; - rect.left = 0; - rect.top = 0; - rect.right = DipToPixelX(size.width); - rect.bottom = DipToPixelY(size.height); - if (!AdjustWindowRectEx(&rect, window_style, FALSE, window_ex_style)) - throw Win32Error(::GetLastError(), "Failed to invoke AdjustWindowRectEx."); - - if (!SetWindowPos(hwnd_, nullptr, 0, 0, rect.right - rect.left, - rect.bottom - rect.top, SWP_NOZORDER | SWP_NOMOVE)) - throw Win32Error(::GetLastError(), "Failed to invoke SetWindowPos."); -} - -Rect WinNativeWindow::GetWindowRect() { - RECT rect; - if (!::GetWindowRect(hwnd_, &rect)) - throw Win32Error(::GetLastError(), "Failed to invoke GetWindowRect."); - - return Rect::FromVertices(PixelToDipX(rect.left), PixelToDipY(rect.top), - PixelToDipX(rect.right), PixelToDipY(rect.bottom)); -} - -void WinNativeWindow::SetWindowRect(const Rect& rect) { - if (!SetWindowPos(hwnd_, nullptr, DipToPixelX(rect.left), - DipToPixelY(rect.top), DipToPixelX(rect.GetRight()), - DipToPixelY(rect.GetBottom()), SWP_NOZORDER)) - throw Win32Error(::GetLastError(), "Failed to invoke SetWindowPos."); -} - -Point WinNativeWindow::GetMousePosition() { - POINT p; - if (!::GetCursorPos(&p)) - throw Win32Error(::GetLastError(), "Failed to get cursor position."); - if (!::ScreenToClient(hwnd_, &p)) - throw Win32Error(::GetLastError(), "Failed to call ScreenToClient."); - return PiToDip(p); -} - -bool WinNativeWindow::CaptureMouse() { - ::SetCapture(hwnd_); - return true; -} - -bool WinNativeWindow::ReleaseMouse() { - const auto result = ::ReleaseCapture(); - return result != 0; -} - -void WinNativeWindow::RequestRepaint() { - log::Debug("A repaint is requested."); - if (!::InvalidateRect(hwnd_, nullptr, FALSE)) - throw Win32Error(::GetLastError(), "Failed to invalidate window."); - if (!::UpdateWindow(hwnd_)) - throw Win32Error(::GetLastError(), "Failed to update window."); -} - -std::unique_ptr WinNativeWindow::BeginPaint() { - return std::make_unique(window_render_target_.get()); -} - -void WinNativeWindow::SetCursor(std::shared_ptr cursor) { - if (cursor == nullptr) { - throw std::runtime_error("Can't use a nullptr as cursor."); - } - - cursor_ = CheckPlatform(cursor, GetPlatformId()); - - if (!::SetClassLongPtrW(hwnd_, GCLP_HCURSOR, - reinterpret_cast(cursor_->GetHandle()))) { - log::Warn( - "Failed to set cursor because failed to set class long. Last error " - "code: {}.", - ::GetLastError()); - return; - } - - if (!IsVisible()) return; - - auto lg = [](const std::string_view& reason) { - log::Warn( - "Failed to set cursor because {} when window is visible. (We need " - "to update cursor if it is inside the window.) Last error code: {}.", - reason, ::GetLastError()); - }; - - ::POINT point; - if (!::GetCursorPos(&point)) { - lg("failed to get cursor pos"); - return; - } - - ::RECT rect; - if (!::GetClientRect(hwnd_, &rect)) { - lg("failed to get window's client rect"); - return; - } - - ::POINT lefttop{rect.left, rect.top}; - ::POINT rightbottom{rect.right, rect.bottom}; - if (!::ClientToScreen(hwnd_, &lefttop)) { - lg("failed to call ClientToScreen on lefttop"); - return; - } - - if (!::ClientToScreen(hwnd_, &rightbottom)) { - lg("failed to call ClientToScreen on rightbottom"); - return; - } - - if (point.x >= lefttop.x && point.y >= lefttop.y && - point.x <= rightbottom.x && point.y <= rightbottom.y) { - ::SetCursor(cursor_->GetHandle()); - } -} - -bool WinNativeWindow::HandleNativeWindowMessage(HWND hwnd, UINT msg, - WPARAM w_param, LPARAM l_param, - LRESULT* result) { - WindowNativeMessageEventArgs args{ - WindowNativeMessage{hwnd, msg, w_param, l_param}}; - native_message_event_.Raise(args); - if (args.IsHandled()) { - *result = args.GetResult(); - return true; - } - - switch (msg) { - case WM_PAINT: - OnPaintInternal(); - *result = 0; - return true; - case WM_ERASEBKGND: - *result = 1; - return true; - case WM_SETFOCUS: - OnSetFocusInternal(); - *result = 0; - return true; - case WM_KILLFOCUS: - OnKillFocusInternal(); - *result = 0; - return true; - case WM_MOUSEMOVE: { - POINT point; - point.x = GET_X_LPARAM(l_param); - point.y = GET_Y_LPARAM(l_param); - OnMouseMoveInternal(point); - *result = 0; - return true; - } - case WM_LBUTTONDOWN: { - POINT point; - point.x = GET_X_LPARAM(l_param); - point.y = GET_Y_LPARAM(l_param); - OnMouseDownInternal(platform::native::mouse_buttons::left, point); - *result = 0; - return true; - } - case WM_LBUTTONUP: { - POINT point; - point.x = GET_X_LPARAM(l_param); - point.y = GET_Y_LPARAM(l_param); - OnMouseUpInternal(platform::native::mouse_buttons::left, point); - *result = 0; - return true; - } - case WM_RBUTTONDOWN: { - POINT point; - point.x = GET_X_LPARAM(l_param); - point.y = GET_Y_LPARAM(l_param); - OnMouseDownInternal(platform::native::mouse_buttons::right, point); - *result = 0; - return true; - } - case WM_RBUTTONUP: { - POINT point; - point.x = GET_X_LPARAM(l_param); - point.y = GET_Y_LPARAM(l_param); - OnMouseUpInternal(platform::native::mouse_buttons::right, point); - *result = 0; - return true; - } - case WM_MBUTTONDOWN: { - POINT point; - point.x = GET_X_LPARAM(l_param); - point.y = GET_Y_LPARAM(l_param); - OnMouseDownInternal(platform::native::mouse_buttons::middle, point); - *result = 0; - return true; - } - case WM_MBUTTONUP: { - POINT point; - point.x = GET_X_LPARAM(l_param); - point.y = GET_Y_LPARAM(l_param); - OnMouseUpInternal(platform::native::mouse_buttons::middle, point); - *result = 0; - return true; - } - case WM_MOUSEWHEEL: - POINT point; - point.x = GET_X_LPARAM(l_param); - point.y = GET_Y_LPARAM(l_param); - ScreenToClient(hwnd, &point); - OnMouseWheelInternal(GET_WHEEL_DELTA_WPARAM(w_param), point); - *result = 0; - return true; - case WM_KEYDOWN: - OnKeyDownInternal(static_cast(w_param)); - *result = 0; - return true; - case WM_KEYUP: - OnKeyUpInternal(static_cast(w_param)); - *result = 0; - return true; - case WM_SYSKEYDOWN: - if (l_param & (1 << 29)) { - OnKeyDownInternal(static_cast(w_param)); - *result = 0; - return true; - } - return false; - case WM_SYSKEYUP: - if (l_param & (1 << 29)) { - OnKeyUpInternal(static_cast(w_param)); - *result = 0; - return true; - } - return false; - case WM_SIZE: - OnResizeInternal(LOWORD(l_param), HIWORD(l_param)); - *result = 0; - return true; - case WM_ACTIVATE: - if (w_param == WA_ACTIVE || w_param == WA_CLICKACTIVE) - OnActivatedInternal(); - else if (w_param == WA_INACTIVE) - OnDeactivatedInternal(); - *result = 0; - return true; - case WM_DESTROY: - OnDestroyInternal(); - *result = 0; - return true; - case WM_IME_SETCONTEXT: - l_param &= ~ISC_SHOWUICOMPOSITIONWINDOW; - *result = ::DefWindowProcW(hwnd, msg, w_param, l_param); - return true; - // We must block these message from DefWindowProc or it will create - // an ugly composition window. - case WM_IME_STARTCOMPOSITION: - case WM_IME_COMPOSITION: - *result = 0; - return true; - default: - return false; - } -} - -RECT WinNativeWindow::GetClientRectPixel() { - RECT rect; - if (!GetClientRect(hwnd_, &rect)) - throw Win32Error(::GetLastError(), "Failed to invoke GetClientRect."); - return rect; -} - -void WinNativeWindow::OnDestroyInternal() { - application_->GetWindowManager()->UnregisterWindow(hwnd_); - hwnd_ = nullptr; - destroy_event_.Raise(nullptr); - if (!sync_flag_) { - sync_flag_ = true; - delete this; - } -} - -void WinNativeWindow::OnPaintInternal() { - paint_event_.Raise(nullptr); - ValidateRect(hwnd_, nullptr); -} - -void WinNativeWindow::OnResizeInternal(const int new_width, - const int new_height) { - if (!(new_width == 0 && new_height == 0)) { - window_render_target_->ResizeBuffer(new_width, new_height); - resize_event_.Raise(Size{PixelToDipX(new_width), PixelToDipY(new_height)}); - } -} - -void WinNativeWindow::OnSetFocusInternal() { - has_focus_ = true; - focus_event_.Raise(FocusChangeType::Gain); -} - -void WinNativeWindow::OnKillFocusInternal() { - has_focus_ = false; - focus_event_.Raise(FocusChangeType::Lost); -} - -void WinNativeWindow::OnMouseMoveInternal(const POINT point) { - // when mouse was previous outside the window - if (!is_mouse_in_) { - // invoke TrackMouseEvent to have WM_MOUSELEAVE sent. - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof tme; - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = hwnd_; - - TrackMouseEvent(&tme); - - is_mouse_in_ = true; - mouse_enter_leave_event_.Raise(MouseEnterLeaveType::Enter); - } - - mouse_move_event_.Raise(PiToDip(point)); -} - -void WinNativeWindow::OnMouseLeaveInternal() { - is_mouse_in_ = false; - mouse_enter_leave_event_.Raise(MouseEnterLeaveType::Leave); -} - -void WinNativeWindow::OnMouseDownInternal(platform::native::MouseButton button, - POINT point) { - const auto dip_point = PiToDip(point); - mouse_down_event_.Raise({button, dip_point, RetrieveKeyMofifier()}); -} - -void WinNativeWindow::OnMouseUpInternal(platform::native::MouseButton button, - POINT point) { - const auto dip_point = PiToDip(point); - mouse_up_event_.Raise({button, dip_point, RetrieveKeyMofifier()}); -} - -void WinNativeWindow::OnMouseWheelInternal(short delta, POINT point) { - CRU_UNUSED(delta) - CRU_UNUSED(point) -} - -void WinNativeWindow::OnKeyDownInternal(int virtual_code) { - key_down_event_.Raise( - {VirtualKeyToKeyCode(virtual_code), RetrieveKeyMofifier()}); -} - -void WinNativeWindow::OnKeyUpInternal(int virtual_code) { - key_up_event_.Raise( - {VirtualKeyToKeyCode(virtual_code), RetrieveKeyMofifier()}); -} - -void WinNativeWindow::OnActivatedInternal() {} - -void WinNativeWindow::OnDeactivatedInternal() {} - -void WinNativeWindowResolver::Reset() { - Expects(window_); // already reset, can't reset again - window_ = nullptr; -} - -WinNativeWindow* Resolve(gsl::not_null resolver) { - const auto window = resolver->Resolve(); - return window == nullptr ? nullptr - : CheckPlatform( - window, WinNativeResource::k_platform_id); -} // namespace cru::platform::native::win -} // namespace cru::platform::native::win diff --git a/src/win/string.cpp b/src/win/string.cpp deleted file mode 100644 index 65a280f2..00000000 --- a/src/win/string.cpp +++ /dev/null @@ -1,188 +0,0 @@ -#include "cru/win/String.hpp" - -#include "cru/win/Exception.hpp" - -#include - -namespace cru::platform::win { -std::string ToUtf8String(const std::wstring_view& string) { - if (string.empty()) return std::string{}; - - const auto length = ::WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), - static_cast(string.size()), nullptr, 0, nullptr, nullptr); - if (length == 0) { - throw Win32Error(::GetLastError(), - "Failed to convert wide string to UTF-8."); - } - - std::string result; - result.resize(length); - if (::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), - static_cast(string.size()), result.data(), - static_cast(result.size()), nullptr, - nullptr) == 0) - throw Win32Error(::GetLastError(), - "Failed to convert wide string to UTF-8."); - return result; -} - -std::wstring ToUtf16String(const std::string_view& string) { - if (string.empty()) return std::wstring{}; - - const auto length = - ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, string.data(), - static_cast(string.size()), nullptr, 0); - if (length == 0) { - throw Win32Error(::GetLastError(), - "Failed to convert wide string to UTF-16."); - } - - std::wstring result; - result.resize(length); - if (::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, string.data(), - static_cast(string.size()), result.data(), - static_cast(result.size())) == 0) - throw win::Win32Error(::GetLastError(), - "Failed to convert wide string to UTF-16."); - return result; -} - -template -inline std::enable_if_t, CodePoint> ExtractBits( - UInt n) { - return static_cast(n & ((1u << number_of_bit) - 1)); -} - -CodePoint Utf8Iterator::Next() { - if (position_ == static_cast(string_.length())) return k_code_point_end; - - const auto cu0 = static_cast(string_[position_++]); - - auto read_next_folowing_code = [this]() -> CodePoint { - if (this->position_ == static_cast(string_.length())) - throw TextEncodeException( - "Unexpected end when read continuing byte of multi-byte code point."); - -#ifdef CRU_DEBUG - const auto u = static_cast(string_[position_]); - if (!(u & (1u << 7)) || (u & (1u << 6))) { - throw TextEncodeException( - "Unexpected bad-format (not 0b10xxxxxx) continuing byte of " - "multi-byte code point."); - } -#endif - - return ExtractBits(string_[position_++]); - }; - - if ((1u << 7) & cu0) { - if ((1u << 6) & cu0) { // 2~4-length code point - if ((1u << 5) & cu0) { // 3~4-length code point - if ((1u << 4) & cu0) { // 4-length code point -#ifdef CRU_DEBUG - if (cu0 & (1u << 3)) { - throw TextEncodeException( - "Unexpected bad-format begin byte (not 0b10xxxxxx) of 4-byte " - "code point."); - } -#endif - - const CodePoint s0 = ExtractBits(cu0) << (6 * 3); - const CodePoint s1 = read_next_folowing_code() << (6 * 2); - const CodePoint s2 = read_next_folowing_code() << 6; - const CodePoint s3 = read_next_folowing_code(); - return s0 + s1 + s2 + s3; - } else { // 3-length code point - const CodePoint s0 = ExtractBits(cu0) << (6 * 2); - const CodePoint s1 = read_next_folowing_code() << 6; - const CodePoint s2 = read_next_folowing_code(); - return s0 + s1 + s2; - } - } else { // 2-length code point - const CodePoint s0 = ExtractBits(cu0) << 6; - const CodePoint s1 = read_next_folowing_code(); - return s0 + s1; - } - } else { - throw TextEncodeException( - "Unexpected bad-format (0b10xxxxxx) begin byte of a code point."); - } - } else { - return static_cast(cu0); - } -} - -CodePoint Utf16Iterator::Next() { - if (position_ == static_cast(string_.length())) return k_code_point_end; - - const auto cu0 = static_cast(string_[position_++]); - - if (cu0 < 0xd800u || cu0 >= 0xe000u) { // 1-length code point - return static_cast(cu0); - } else if (cu0 <= 0xdbffu) { // 2-length code point - if (position_ == static_cast(string_.length())) { - throw TextEncodeException( - "Unexpected end when reading second code unit of surrogate pair."); - } - const auto cu1 = static_cast(string_[position_++]); - -#ifdef CRU_DEBUG - if (cu1 < 0xDC00u || cu1 > 0xdfffu) { - throw TextEncodeException( - "Unexpected bad-format second code unit of surrogate pair."); - } -#endif - - const auto s0 = ExtractBits(cu0) << 10; - const auto s1 = ExtractBits(cu1); - - return s0 + s1 + 0x10000; - - } else { - throw TextEncodeException( - "Unexpected bad-format first code unit of surrogate pair."); - } -} - -int IndexUtf8ToUtf16(const std::string_view& utf8_string, int utf8_index, - const std::wstring_view& utf16_string) { - if (utf8_index >= static_cast(utf8_string.length())) - return static_cast(utf16_string.length()); - - int cp_index = 0; - Utf8Iterator iter{utf8_string}; - while (iter.CurrentPosition() <= utf8_index) { - iter.Next(); - cp_index++; - } - - Utf16Iterator result_iter{utf16_string}; - for (int i = 0; i < cp_index - 1; i++) { - if (result_iter.Next() == k_code_point_end) break; - } - - return result_iter.CurrentPosition(); -} - -int IndexUtf16ToUtf8(const std::wstring_view& utf16_string, int utf16_index, - const std::string_view& utf8_string) { - if (utf16_index >= static_cast(utf16_string.length())) - return static_cast(utf8_string.length()); - - int cp_index = 0; - Utf16Iterator iter{utf16_string}; - while (iter.CurrentPosition() <= utf16_index) { - iter.Next(); - cp_index++; - } - - Utf8Iterator result_iter{utf8_string}; - for (int i = 0; i < cp_index - 1; i++) { - if (result_iter.Next() == k_code_point_end) break; - } - - return result_iter.CurrentPosition(); -} - -} // namespace cru::platform::win diff --git a/test/win/String.cpp b/test/win/String.cpp new file mode 100644 index 00000000..a666dc81 --- /dev/null +++ b/test/win/String.cpp @@ -0,0 +1,53 @@ +#include "cru/win/String.hpp" + +#include + +using cru::platform::win::k_code_point_end; + +TEST(WinString, Utf8Iterator) { + using cru::platform::win::Utf8Iterator; + std::string_view text = "aπ你🤣!"; + Utf8Iterator i{text}; + ASSERT_EQ(i.Next(), 0x0061); + ASSERT_EQ(i.Next(), 0x03C0); + ASSERT_EQ(i.Next(), 0x4F60); + ASSERT_EQ(i.Next(), 0x1F923); + ASSERT_EQ(i.Next(), 0x0021); + ASSERT_EQ(i.Next(), k_code_point_end); +} + +TEST(WinString, Utf16Iterator) { + using cru::platform::win::Utf16Iterator; + std::wstring_view text = L"aπ你🤣!"; + Utf16Iterator i{text}; + ASSERT_EQ(i.Next(), 0x0061); + ASSERT_EQ(i.Next(), 0x03C0); + ASSERT_EQ(i.Next(), 0x4F60); + ASSERT_EQ(i.Next(), 0x1F923); + ASSERT_EQ(i.Next(), 0x0021); + ASSERT_EQ(i.Next(), k_code_point_end); +} + +TEST(WinString, IndexUtf8ToUtf16) { + using cru::platform::win::IndexUtf8ToUtf16; + std::string_view utf8_string = "aπ你🤣!"; + std::wstring_view utf16_string = L"aπ你🤣!"; + ASSERT_EQ(IndexUtf8ToUtf16(utf8_string, 0, utf16_string), 0); + ASSERT_EQ(IndexUtf8ToUtf16(utf8_string, 1, utf16_string), 1); + ASSERT_EQ(IndexUtf8ToUtf16(utf8_string, 3, utf16_string), 2); + ASSERT_EQ(IndexUtf8ToUtf16(utf8_string, 6, utf16_string), 3); + ASSERT_EQ(IndexUtf8ToUtf16(utf8_string, 10, utf16_string), 5); + ASSERT_EQ(IndexUtf8ToUtf16(utf8_string, 11, utf16_string), 6); +} + +TEST(WinString, IndexUtf16ToUtf8) { + using cru::platform::win::IndexUtf16ToUtf8; + std::string_view utf8_string = "aπ你🤣!"; + std::wstring_view utf16_string = L"aπ你🤣!"; + ASSERT_EQ(IndexUtf16ToUtf8(utf16_string, 0, utf8_string), 0); + ASSERT_EQ(IndexUtf16ToUtf8(utf16_string, 1, utf8_string), 1); + ASSERT_EQ(IndexUtf16ToUtf8(utf16_string, 2, utf8_string), 3); + ASSERT_EQ(IndexUtf16ToUtf8(utf16_string, 3, utf8_string), 6); + ASSERT_EQ(IndexUtf16ToUtf8(utf16_string, 5, utf8_string), 10); + ASSERT_EQ(IndexUtf16ToUtf8(utf16_string, 6, utf8_string), 11); +} diff --git a/test/win/string.cpp b/test/win/string.cpp deleted file mode 100644 index a666dc81..00000000 --- a/test/win/string.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "cru/win/String.hpp" - -#include - -using cru::platform::win::k_code_point_end; - -TEST(WinString, Utf8Iterator) { - using cru::platform::win::Utf8Iterator; - std::string_view text = "aπ你🤣!"; - Utf8Iterator i{text}; - ASSERT_EQ(i.Next(), 0x0061); - ASSERT_EQ(i.Next(), 0x03C0); - ASSERT_EQ(i.Next(), 0x4F60); - ASSERT_EQ(i.Next(), 0x1F923); - ASSERT_EQ(i.Next(), 0x0021); - ASSERT_EQ(i.Next(), k_code_point_end); -} - -TEST(WinString, Utf16Iterator) { - using cru::platform::win::Utf16Iterator; - std::wstring_view text = L"aπ你🤣!"; - Utf16Iterator i{text}; - ASSERT_EQ(i.Next(), 0x0061); - ASSERT_EQ(i.Next(), 0x03C0); - ASSERT_EQ(i.Next(), 0x4F60); - ASSERT_EQ(i.Next(), 0x1F923); - ASSERT_EQ(i.Next(), 0x0021); - ASSERT_EQ(i.Next(), k_code_point_end); -} - -TEST(WinString, IndexUtf8ToUtf16) { - using cru::platform::win::IndexUtf8ToUtf16; - std::string_view utf8_string = "aπ你🤣!"; - std::wstring_view utf16_string = L"aπ你🤣!"; - ASSERT_EQ(IndexUtf8ToUtf16(utf8_string, 0, utf16_string), 0); - ASSERT_EQ(IndexUtf8ToUtf16(utf8_string, 1, utf16_string), 1); - ASSERT_EQ(IndexUtf8ToUtf16(utf8_string, 3, utf16_string), 2); - ASSERT_EQ(IndexUtf8ToUtf16(utf8_string, 6, utf16_string), 3); - ASSERT_EQ(IndexUtf8ToUtf16(utf8_string, 10, utf16_string), 5); - ASSERT_EQ(IndexUtf8ToUtf16(utf8_string, 11, utf16_string), 6); -} - -TEST(WinString, IndexUtf16ToUtf8) { - using cru::platform::win::IndexUtf16ToUtf8; - std::string_view utf8_string = "aπ你🤣!"; - std::wstring_view utf16_string = L"aπ你🤣!"; - ASSERT_EQ(IndexUtf16ToUtf8(utf16_string, 0, utf8_string), 0); - ASSERT_EQ(IndexUtf16ToUtf8(utf16_string, 1, utf8_string), 1); - ASSERT_EQ(IndexUtf16ToUtf8(utf16_string, 2, utf8_string), 3); - ASSERT_EQ(IndexUtf16ToUtf8(utf16_string, 3, utf8_string), 6); - ASSERT_EQ(IndexUtf16ToUtf8(utf16_string, 5, utf8_string), 10); - ASSERT_EQ(IndexUtf16ToUtf8(utf16_string, 6, utf8_string), 11); -} -- cgit v1.2.3