diff options
Diffstat (limited to 'CruUI-Generate/cru_ui.hpp')
-rw-r--r-- | CruUI-Generate/cru_ui.hpp | 3165 |
1 files changed, 3165 insertions, 0 deletions
diff --git a/CruUI-Generate/cru_ui.hpp b/CruUI-Generate/cru_ui.hpp new file mode 100644 index 00000000..9941533e --- /dev/null +++ b/CruUI-Generate/cru_ui.hpp @@ -0,0 +1,3165 @@ +#pragma once +//-------------------------------------------------------- +//-------begin of file: ..\..\src\any_map.hpp +//-------------------------------------------------------- + +#include <any> +#include <unordered_map> +#include <functional> +#include <optional> +#include <typeinfo> + +//-------------------------------------------------------- +//-------begin of file: ..\..\src\base.hpp +//-------------------------------------------------------- + +// ReSharper disable once CppUnusedIncludeDirective +//-------------------------------------------------------- +//-------begin of file: ..\..\src\global_macros.hpp +//-------------------------------------------------------- + +#ifdef _DEBUG +#define CRU_DEBUG +#endif + +#ifdef CRU_DEBUG +#define CRU_DEBUG_LAYOUT +#endif +//-------------------------------------------------------- +//-------end of file: ..\..\src\global_macros.hpp +//-------------------------------------------------------- + + +#include <string> +#include <stdexcept> +#include <string_view> +#include <chrono> +#include <optional> +// ReSharper disable once CppUnusedIncludeDirective +#include <type_traits> + +namespace cru +{ + template<typename T> struct type_tag {}; + + //typedefs + using String = std::wstring; + using MultiByteString = std::string; + + using StringView = std::wstring_view; + using MultiByteStringView = std::string_view; + + using FloatSecond = std::chrono::duration<double, std::chrono::seconds::period>; + + enum class FlowControl + { + Continue, + Break + }; + + + class Object + { + public: + Object() = default; + Object(const Object&) = default; + Object& operator = (const Object&) = default; + Object(Object&&) = default; + Object& operator = (Object&&) = default; + virtual ~Object() = default; + }; + + struct Interface + { + virtual ~Interface() = default; + }; + + [[noreturn]] inline void UnreachableCode() + { + throw std::logic_error("Unreachable code."); + } + + MultiByteString ToUtf8String(const StringView& string); + + inline void Require(const bool condition, const MultiByteStringView& error_message) + { + if (!condition) + throw std::invalid_argument(error_message.data()); + } + + template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> + float Coerce(const T n, const std::optional<T> min, const std::optional<T> max) + { + if (min.has_value() && n < min.value()) + return min.value(); + if (max.has_value() && n > max.value()) + return max.value(); + return n; + } + + template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> + float Coerce(const T n, const std::nullopt_t, const std::optional<T> max) + { + if (max.has_value() && n > max.value()) + return max.value(); + return n; + } + + template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> + float Coerce(const T n, const std::optional<T> min, const std::nullopt_t) + { + if (min.has_value() && n < min.value()) + return min.value(); + return n; + } + +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\base.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\format.hpp +//-------------------------------------------------------- + + +namespace cru +{ + namespace details + { + constexpr StringView PlaceHolder(type_tag<String>) + { + return StringView(L"{}"); + } + + constexpr MultiByteStringView PlaceHolder(type_tag<MultiByteString>) + { + return MultiByteStringView("{}"); + } + + template<typename TString> + void FormatInternal(TString& string) + { + const auto find_result = string.find(PlaceHolder(type_tag<TString>{})); + if (find_result != TString::npos) + throw std::invalid_argument("There is more placeholders than args."); + } + + template<typename TString, typename T, typename... TRest> + void FormatInternal(TString& string, const T& arg, const TRest&... args) + { + const auto find_result = string.find(PlaceHolder(type_tag<TString>{})); + if (find_result == TString::npos) + throw std::invalid_argument("There is less placeholders than args."); + + string.replace(find_result, 2, FormatToString(arg, type_tag<TString>{})); + FormatInternal<TString>(string, args...); + } + } + + template<typename... T> + String Format(const StringView& format, const T&... args) + { + String result(format); + details::FormatInternal<String>(result, args...); + return result; + } + + template<typename... T> + MultiByteString Format(const MultiByteStringView& format, const T&... args) + { + MultiByteString result(format); + details::FormatInternal<MultiByteString>(result, args...); + return result; + } + +#define CRU_FORMAT_NUMBER(type) \ + inline String FormatToString(const type number, type_tag<String>) \ + { \ + return std::to_wstring(number); \ + } \ + inline MultiByteString FormatToString(const type number, type_tag<MultiByteString>) \ + { \ + return std::to_string(number); \ + } + + CRU_FORMAT_NUMBER(int) + CRU_FORMAT_NUMBER(short) + CRU_FORMAT_NUMBER(long) + CRU_FORMAT_NUMBER(long long) + CRU_FORMAT_NUMBER(unsigned int) + CRU_FORMAT_NUMBER(unsigned short) + CRU_FORMAT_NUMBER(unsigned long) + CRU_FORMAT_NUMBER(unsigned long long) + CRU_FORMAT_NUMBER(float) + CRU_FORMAT_NUMBER(double) + +#undef CRU_FORMAT_NUMBER + + inline StringView FormatToString(const String& string, type_tag<String>) + { + return string; + } + + inline MultiByteString FormatToString(const MultiByteString& string, type_tag<MultiByteString>) + { + return string; + } + + inline StringView FormatToString(const StringView& string, type_tag<String>) + { + return string; + } + + inline MultiByteStringView FormatToString(const MultiByteStringView& string, type_tag<MultiByteString>) + { + return string; + } + + inline StringView FormatToString(const wchar_t* string, type_tag<String>) + { + return StringView(string); + } + + inline MultiByteStringView FormatToString(const char* string, type_tag<MultiByteString>) + { + return MultiByteString(string); + } +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\format.hpp +//-------------------------------------------------------- + + +namespace cru +{ + // A map with String as key and any type as value. + // It also has notification when value with specified key changed. + class AnyMap : public Object + { + public: + using ListenerToken = long; + using Listener = std::function<void(const std::any&)>; + + AnyMap() = default; + AnyMap(const AnyMap& other) = delete; + AnyMap(AnyMap&& other) = delete; + AnyMap& operator=(const AnyMap& other) = delete; + AnyMap& operator=(AnyMap&& other) = delete; + ~AnyMap() override = default; + + + // return the value if the value exists and the type of value is T. + // return a null optional if value doesn't exists. + // throw std::runtime_error if type is mismatch. + template <typename T> + std::optional<T> GetOptionalValue(const String& key) const + { + try + { + const auto find_result = map_.find(key); + if (find_result != map_.cend()) + { + const auto& value = find_result->second.first; + if (value.has_value()) + return std::any_cast<T>(value); + return std::nullopt; + } + return std::nullopt; + } + catch (const std::bad_any_cast&) + { + throw std::runtime_error(Format("Value of key \"{}\" in AnyMap is not of the type {}.", ToUtf8String(key), typeid(T).name())); + } + } + + // return the value if the value exists and the type of value is T. + // throw if value doesn't exists. (different from "GetOptionalValue"). + // throw std::runtime_error if type is mismatch. + template <typename T> + T GetValue(const String& key) const + { + const auto optional_value = GetOptionalValue<T>(key); + if (optional_value.has_value()) + return optional_value.value(); + else + throw std::runtime_error(Format("Key \"{}\" does not exists in AnyMap.", ToUtf8String(key))); + } + + // Set the value of key, and trigger all related listeners. + template <typename T> + void SetValue(const String& key, T&& value) + { + auto& p = map_[key]; + p.first = std::make_any<T>(std::forward<T>(value)); + InvokeListeners(p.second, p.first); + } + + // Remove the value of the key. + void ClearValue(const String& key) + { + auto& p = map_[key]; + p.first = std::any{}; + InvokeListeners(p.second, std::any{}); + } + + // Add a listener which is called when value of key is changed. + // Return a token used to remove the listener. + ListenerToken RegisterValueChangeListener(const String& key, const Listener& listener); + + // Remove a listener by token. + void UnregisterValueChangeListener(ListenerToken token); + + private: + void InvokeListeners(std::list<ListenerToken>& listener_list, const std::any& value); + + private: + std::unordered_map<String, std::pair<std::any, std::list<ListenerToken>>> map_{}; + std::unordered_map<ListenerToken, Listener> listeners_{}; + ListenerToken current_listener_token_ = 0; + }; +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\any_map.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\application.hpp +//-------------------------------------------------------- + +//-------------------------------------------------------- +//-------begin of file: ..\..\src\system_headers.hpp +//-------------------------------------------------------- + + +//include system headers + +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#include <windowsx.h> + +#pragma comment(lib, "D3D11.lib") +#include <d3d11.h> + +#pragma comment(lib, "D2d1.lib") +#include <d2d1_1.h> + +#pragma comment(lib, "DWrite.lib") +#include <dwrite.h> + +#include <dxgi1_2.h> +#include <wrl/client.h> +//-------------------------------------------------------- +//-------end of file: ..\..\src\system_headers.hpp +//-------------------------------------------------------- +#include <memory> +#include <optional> +#include <functional> +#include <typeindex> +#include <type_traits> + + +#ifdef CRU_DEBUG +#include <unordered_set> +#endif + +namespace cru +{ + class Application; + + namespace ui + { + class WindowClass; + } + + + class GodWindow : public Object + { + public: + explicit GodWindow(Application* application); + GodWindow(const GodWindow& other) = delete; + GodWindow(GodWindow&& other) = delete; + GodWindow& operator=(const GodWindow& other) = delete; + GodWindow& operator=(GodWindow&& other) = delete; + ~GodWindow() override; + + HWND GetHandle() const + { + return hwnd_; + } + + std::optional<LRESULT> HandleGodWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param); + + private: + std::unique_ptr<ui::WindowClass> god_window_class_; + HWND hwnd_; + }; + + class Application : public Object + { + public: + static Application* GetInstance(); + private: + static Application* instance_; + + public: + explicit Application(HINSTANCE h_instance); + Application(const Application&) = delete; + Application(Application&&) = delete; + Application& operator = (const Application&) = delete; + Application& operator = (Application&&) = delete; + ~Application() override; + + public: + int Run(); + void Quit(int quit_code); + + + HINSTANCE GetInstanceHandle() const + { + return h_instance_; + } + + GodWindow* GetGodWindow() const + { + return god_window_.get(); + } + + // Resolve a singleton. + // All singletons will be delete in reverse order of resolve. + template<typename T, typename = std::enable_if_t<std::is_base_of_v<Object, T>>> + T* ResolveSingleton(const std::function<T*(Application*)>& creator) + { + const auto& index = std::type_index{typeid(T)}; + const auto find_result = singleton_map_.find(index); + if (find_result != singleton_map_.cend()) + return static_cast<T*>(find_result->second); + +#ifdef CRU_DEBUG + const auto type_find_result = singleton_type_set_.find(index); + if (type_find_result != singleton_type_set_.cend()) + throw std::logic_error("The singleton of that type is being constructed. This may cause a dead recursion."); + singleton_type_set_.insert(index); +#endif + + auto singleton = creator(this); + singleton_map_.emplace(index, static_cast<Object*>(singleton)); + singleton_list_.push_back(singleton); + return singleton; + } + + private: + HINSTANCE h_instance_; + + std::unique_ptr<GodWindow> god_window_; + + std::unordered_map<std::type_index, Object*> singleton_map_; + std::list<Object*> singleton_list_; // used for reverse destroy. +#ifdef CRU_DEBUG + std::unordered_set<std::type_index> singleton_type_set_; // used for dead recursion. +#endif + }; + + + void InvokeLater(const std::function<void()>& action); +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\application.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\exception.hpp +//-------------------------------------------------------- + +#include <optional> + + + +namespace cru { + class HResultError : public std::runtime_error + { + public: + explicit HResultError(HRESULT h_result, std::optional<MultiByteStringView> additional_message = std::nullopt); + HResultError(const HResultError& other) = default; + HResultError(HResultError&& other) = default; + HResultError& operator=(const HResultError& other) = default; + HResultError& operator=(HResultError&& other) = default; + ~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 MultiByteStringView& message) { + if (FAILED(h_result)) + throw HResultError(h_result, message); + } + + class Win32Error : public std::runtime_error + { + public: + explicit Win32Error(DWORD error_code, std::optional<MultiByteStringView> additional_message = std::nullopt); + Win32Error(const Win32Error& other) = default; + Win32Error(Win32Error&& other) = default; + Win32Error& operator=(const Win32Error& other) = default; + Win32Error& operator=(Win32Error&& other) = default; + ~Win32Error() override = default; + + HRESULT GetErrorCode() const + { + return error_code_; + } + + private: + DWORD error_code_; + }; +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\exception.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\timer.hpp +//-------------------------------------------------------- + + +#include <map> +#include <chrono> +#include <functional> +#include <optional> + + +namespace cru +{ + using TimerAction = std::function<void()>; + + class TimerManager : public Object + { + public: + static TimerManager* GetInstance(); + + private: + TimerManager() = default; + public: + TimerManager(const TimerManager& other) = delete; + TimerManager(TimerManager&& other) = delete; + TimerManager& operator=(const TimerManager& other) = delete; + TimerManager& operator=(TimerManager&& other) = delete; + ~TimerManager() override = default; + + UINT_PTR CreateTimer(UINT milliseconds, bool loop, const TimerAction& action); + void KillTimer(UINT_PTR id); + std::optional<std::pair<bool, TimerAction>> GetAction(UINT_PTR id); + + private: + std::map<UINT_PTR, std::pair<bool, TimerAction>> map_{}; + UINT_PTR current_count_ = 0; + }; + + class TimerTask + { + friend TimerTask SetTimeout(std::chrono::milliseconds milliseconds, const TimerAction& action); + friend TimerTask SetInterval(std::chrono::milliseconds milliseconds, const TimerAction& action); + + private: + explicit TimerTask(UINT_PTR id); + + public: + TimerTask(const TimerTask& other) = default; + TimerTask(TimerTask&& other) = default; + TimerTask& operator=(const TimerTask& other) = default; + TimerTask& operator=(TimerTask&& other) = default; + ~TimerTask() = default; + + void Cancel() const; + + private: + UINT_PTR id_; + }; + + TimerTask SetTimeout(std::chrono::milliseconds milliseconds, const TimerAction& action); + TimerTask SetInterval(std::chrono::milliseconds milliseconds, const TimerAction& action); +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\timer.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\ui\window.hpp +//-------------------------------------------------------- + +#include <map> +#include <list> +#include <memory> + +//-------------------------------------------------------- +//-------begin of file: ..\..\src\ui\control.hpp +//-------------------------------------------------------- + +#include <unordered_map> +#include <any> +#include <utility> + +//-------------------------------------------------------- +//-------begin of file: ..\..\src\ui\ui_base.hpp +//-------------------------------------------------------- + +#include <optional> + + +namespace cru::ui +{ + struct Point + { + constexpr static Point Zero() + { + return Point(0, 0); + } + + constexpr Point() = default; + constexpr Point(const float x, const float y) : x(x), y(y) { } + + float x = 0; + float y = 0; + }; + + constexpr bool operator==(const Point& left, const Point& right) + { + return left.x == right.x && left.y == right.y; + } + + constexpr bool operator!=(const Point& left, const Point& right) + { + return !(left == right); + } + + struct Size + { + constexpr static Size Zero() + { + return Size(0, 0); + } + + constexpr Size() = default; + constexpr Size(const float width, const float height) : width(width), height(height) { } + + float width = 0; + float height = 0; + }; + + constexpr Size operator + (const Size& left, const Size& right) + { + return Size(left.width + right.width, left.height + right.height); + } + + constexpr Size operator - (const Size& left, const Size& right) + { + return Size(left.width - right.width, left.height - right.height); + } + + constexpr bool operator==(const Size& left, const Size& right) + { + return left.width == right.width && left.height == right.height; + } + + constexpr bool operator!=(const Size& left, const Size& right) + { + return !(left == right); + } + + struct Thickness + { + constexpr static Thickness Zero() + { + return Thickness(0); + } + + constexpr Thickness() : Thickness(0) { } + + constexpr explicit Thickness(const float width) + : left(width), top(width), right(width), bottom(width) { } + + constexpr explicit Thickness(const float horizontal, const float vertical) + : left(horizontal), top(vertical), right(horizontal), bottom(vertical) { } + + constexpr Thickness(const float left, const float top, const float right, const float bottom) + : left(left), top(top), right(right), bottom(bottom) { } + + float GetHorizontalTotal() const + { + return left + right; + } + + float GetVerticalTotal() const + { + return top + bottom; + } + + float Validate() const + { + return left >= 0.0 && top >= 0.0 && right >= 0.0 && bottom >= 0.0; + } + + float left; + float top; + float right; + float bottom; + }; + + struct Rect + { + constexpr Rect() = default; + constexpr Rect(const float left, const float top, const float width, const float height) + : left(left), top(top), width(width), height(height) { } + constexpr Rect(const Point& lefttop, const Size& size) + : left(lefttop.x), top(lefttop.y), width(size.width), height(size.height) { } + + constexpr static Rect FromVertices(const float left, const float top, const float right, const float bottom) + { + return Rect(left, top, right - left, bottom - top); + } + + constexpr float GetRight() const + { + return left + width; + } + + constexpr float GetBottom() const + { + return top + height; + } + + constexpr Point GetLeftTop() const + { + return Point(left, top); + } + + constexpr Point GetRightBottom() const + { + return Point(left + width, top + height); + } + + constexpr Size GetSize() const + { + return Size(width, height); + } + + constexpr Rect Shrink(const Thickness& thickness) const + { + return Rect(left + thickness.left, top + thickness.top, width - thickness.GetHorizontalTotal(), height - thickness.GetVerticalTotal()); + } + + constexpr bool IsPointInside(const Point& point) const + { + return + point.x >= left && + point.x < GetRight() && + point.y >= top && + point.y < GetBottom(); + } + + float left = 0.0f; + float top = 0.0f; + float width = 0.0f; + float height = 0.0f; + }; + + enum class MouseButton + { + Left, + Right, + Middle + }; + + struct TextRange + { + constexpr static std::optional<TextRange> FromTwoSides(unsigned first, unsigned second) + { + if (first > second) + return std::make_optional<TextRange>(second, first - second); + if (first < second) + return std::make_optional<TextRange>(first, second - first); + return std::nullopt; + } + + constexpr static std::pair<unsigned, unsigned> ToTwoSides(std::optional<TextRange> text_range, unsigned default_position = 0) + { + if (text_range.has_value()) + return std::make_pair(text_range.value().position, text_range.value().position + text_range.value().count); + return std::make_pair(default_position, default_position); + } + + constexpr TextRange() = default; + constexpr TextRange(const unsigned position, const unsigned count) + : position(position), count(count) + { + + } + + unsigned position = 0; + unsigned count = 0; + }; + + bool IsKeyDown(int virtual_code); + bool IsKeyToggled(int virtual_code); + bool IsAnyMouseButtonDown(); +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\ui\ui_base.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\ui\layout_base.hpp +//-------------------------------------------------------- + +#include <unordered_set> + + +namespace cru::ui +{ + class Control; + class Window; + + enum class Alignment + { + Center, + Start, + End + }; + + enum class MeasureMode + { + Exactly, + Content, + Stretch + }; + + enum class RectRange + { + Content, // content excluding padding, border and margin + Padding, // only including content and padding + HalfBorder, // including content, padding and half border + FullBorder, // including content, padding and full border + Margin // including content, padding, border and margin + }; + + struct LayoutSideParams final + { + constexpr static LayoutSideParams Exactly(const float length, const Alignment alignment = Alignment::Center) + { + return LayoutSideParams(MeasureMode::Exactly, length, alignment); + } + + constexpr static LayoutSideParams Content(const Alignment alignment = Alignment::Center) + { + return LayoutSideParams(MeasureMode::Content, 0, alignment); + } + + constexpr static LayoutSideParams Stretch(const Alignment alignment = Alignment::Center) + { + return LayoutSideParams(MeasureMode::Stretch, 0, alignment); + } + + constexpr LayoutSideParams() = default; + + constexpr explicit LayoutSideParams(const MeasureMode mode, const float length, const Alignment alignment) + : length(length), mode(mode), alignment(alignment) + { + + } + + constexpr bool Validate() const + { + if (length < 0.0) + return false; + if (min.has_value() && min.value() < 0.0) + return false; + if (max.has_value() && max.value() < 0.0) + return false; + if (min.has_value() && max.has_value() && min.value() > max.value()) + return false; + return true; + } + + // only used in exactly mode, specify the exactly side length of content. + float length = 0.0; + MeasureMode mode = MeasureMode::Content; + Alignment alignment = Alignment::Center; + + // min and max specify the min/max side length of content. + // they are used as hint and respect the actual size that content needs. + // when mode is exactly, length is coerced into the min-max range. + std::optional<float> min = std::nullopt; + std::optional<float> max = std::nullopt; + }; + + struct BasicLayoutParams final + { + BasicLayoutParams() = default; + BasicLayoutParams(const BasicLayoutParams&) = default; + BasicLayoutParams(BasicLayoutParams&&) = default; + BasicLayoutParams& operator = (const BasicLayoutParams&) = default; + BasicLayoutParams& operator = (BasicLayoutParams&&) = default; + ~BasicLayoutParams() = default; + + bool Validate() const + { + return width.Validate() && height.Validate() && margin.Validate() && padding.Validate(); + } + + LayoutSideParams width; + LayoutSideParams height; + Thickness padding; + Thickness margin; + }; + + + class LayoutManager : public Object + { + public: + static LayoutManager* GetInstance(); + private: + LayoutManager() = default; + public: + LayoutManager(const LayoutManager& other) = delete; + LayoutManager(LayoutManager&& other) = delete; + LayoutManager& operator=(const LayoutManager& other) = delete; + LayoutManager& operator=(LayoutManager&& other) = delete; + ~LayoutManager() override = default; + + + //*************** region: position cache *************** + + //Mark position cache of the control and its descendants invalid, + //(which is saved as an auto-managed list internal) + //and send a message to refresh them. + void InvalidateControlPositionCache(Control* control); + + //Refresh position cache of the control and its descendants whose cache + //has been marked as invalid. + void RefreshInvalidControlPositionCache(); + + //Refresh position cache of the control and its descendants immediately. + static void RefreshControlPositionCache(Control* control); + + private: + static void RefreshControlPositionCacheInternal(Control* control, const Point& parent_lefttop_absolute); + + private: + std::unordered_set<Control*> cache_invalid_controls_; + std::unordered_set<Window*> layout_invalid_windows_; + }; +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\ui\layout_base.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\ui\events\ui_event.hpp +//-------------------------------------------------------- + +#include <optional> + +//-------------------------------------------------------- +//-------begin of file: ..\..\src\cru_event.hpp +//-------------------------------------------------------- + +#include <type_traits> +#include <functional> +#include <unordered_map> + + +namespace cru { + //Base class of all event args. + class BasicEventArgs : public Object + { + public: + explicit BasicEventArgs(Object* sender) + : sender_(sender) + { + + } + BasicEventArgs(const BasicEventArgs& other) = default; + BasicEventArgs(BasicEventArgs&& other) = default; + BasicEventArgs& operator=(const BasicEventArgs& other) = default; + BasicEventArgs& operator=(BasicEventArgs&& other) = default; + ~BasicEventArgs() override = default; + + //Get the sender of the event. + Object* GetSender() const + { + return sender_; + } + + private: + Object* sender_; + }; + + + //A non-copyable non-movable Event class. + //It stores a list of event handlers. + //TArgsType must be subclass of BasicEventArgs. + template<typename TArgsType> + class Event + { + public: + static_assert(std::is_base_of_v<BasicEventArgs, TArgsType>, + "TArgsType must be subclass of BasicEventArgs."); + + + using ArgsType = TArgsType; + using EventHandler = std::function<void(ArgsType&)>; + using EventHandlerToken = long; + + Event() = default; + Event(const Event&) = delete; + Event& operator = (const Event&) = delete; + Event(Event&&) = delete; + Event& operator = (Event&&) = delete; + ~Event() = default; + + EventHandlerToken AddHandler(const EventHandler& handler) + { + const auto token = current_token_++; + handlers_.emplace(token, handler); + return token; + } + + void RemoveHandler(const EventHandlerToken token) { + auto find_result = handlers_.find(token); + if (find_result != handlers_.cend()) + handlers_.erase(find_result); + } + + void Raise(ArgsType& args) { + for (const auto& handler : handlers_) + (handler.second)(args); + } + + bool IsNoHandler() const + { + return handlers_.empty(); + } + + private: + std::unordered_map<EventHandlerToken, EventHandler> handlers_; + + EventHandlerToken current_token_ = 0; + }; +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\cru_event.hpp +//-------------------------------------------------------- + +namespace cru::ui +{ + class Control; +} + +namespace cru::ui::events +{ + class UiEventArgs : public BasicEventArgs + { + public: + UiEventArgs(Object* sender, Object* original_sender) + : BasicEventArgs(sender), original_sender_(original_sender) + { + + } + + UiEventArgs(const UiEventArgs& other) = default; + UiEventArgs(UiEventArgs&& other) = default; + UiEventArgs& operator=(const UiEventArgs& other) = default; + UiEventArgs& operator=(UiEventArgs&& other) = default; + ~UiEventArgs() override = default; + + Object* GetOriginalSender() const + { + return original_sender_; + } + + private: + Object* original_sender_; + }; + + + class MouseEventArgs : public UiEventArgs + { + public: + MouseEventArgs(Object* sender, Object* original_sender, const std::optional<Point>& point = std::nullopt) + : UiEventArgs(sender, original_sender), point_(point) + { + + } + MouseEventArgs(const MouseEventArgs& other) = default; + MouseEventArgs(MouseEventArgs&& other) = default; + MouseEventArgs& operator=(const MouseEventArgs& other) = default; + MouseEventArgs& operator=(MouseEventArgs&& other) = default; + ~MouseEventArgs() override = default; + + Point GetPoint(Control* control, RectRange range = RectRange::Content) const; + + private: + std::optional<Point> point_; + }; + + + class MouseButtonEventArgs : public MouseEventArgs + { + public: + MouseButtonEventArgs(Object* sender, Object* original_sender, const Point& point, const MouseButton button) + : MouseEventArgs(sender, original_sender, point), button_(button) + { + + } + MouseButtonEventArgs(const MouseButtonEventArgs& other) = default; + MouseButtonEventArgs(MouseButtonEventArgs&& other) = default; + MouseButtonEventArgs& operator=(const MouseButtonEventArgs& other) = default; + MouseButtonEventArgs& operator=(MouseButtonEventArgs&& other) = default; + ~MouseButtonEventArgs() override = default; + + MouseButton GetMouseButton() const + { + return button_; + } + + private: + MouseButton button_; + }; + + + class DrawEventArgs : public UiEventArgs + { + public: + DrawEventArgs(Object* sender, Object* original_sender, ID2D1DeviceContext* device_context) + : UiEventArgs(sender, original_sender), device_context_(device_context) + { + + } + DrawEventArgs(const DrawEventArgs& other) = default; + DrawEventArgs(DrawEventArgs&& other) = default; + DrawEventArgs& operator=(const DrawEventArgs& other) = default; + DrawEventArgs& operator=(DrawEventArgs&& other) = default; + ~DrawEventArgs() = default; + + ID2D1DeviceContext* GetDeviceContext() const + { + return device_context_; + } + + private: + ID2D1DeviceContext * device_context_; + }; + + + class PositionChangedEventArgs : public UiEventArgs + { + public: + PositionChangedEventArgs(Object* sender, Object* original_sender, const Point& old_position, const Point& new_position) + : UiEventArgs(sender, original_sender), old_position_(old_position), new_position_(new_position) + { + + } + PositionChangedEventArgs(const PositionChangedEventArgs& other) = default; + PositionChangedEventArgs(PositionChangedEventArgs&& other) = default; + PositionChangedEventArgs& operator=(const PositionChangedEventArgs& other) = default; + PositionChangedEventArgs& operator=(PositionChangedEventArgs&& other) = default; + ~PositionChangedEventArgs() override = default; + + Point GetOldPosition() const + { + return old_position_; + } + + Point GetNewPosition() const + { + return new_position_; + } + + private: + Point old_position_; + Point new_position_; + }; + + + class SizeChangedEventArgs : public UiEventArgs + { + public: + SizeChangedEventArgs(Object* sender, Object* original_sender, const Size& old_size, const Size& new_size) + : UiEventArgs(sender, original_sender), old_size_(old_size), new_size_(new_size) + { + + } + SizeChangedEventArgs(const SizeChangedEventArgs& other) = default; + SizeChangedEventArgs(SizeChangedEventArgs&& other) = default; + SizeChangedEventArgs& operator=(const SizeChangedEventArgs& other) = default; + SizeChangedEventArgs& operator=(SizeChangedEventArgs&& other) = default; + ~SizeChangedEventArgs() override = default; + + Size GetOldSize() const + { + return old_size_; + } + + Size GetNewSize() const + { + return new_size_; + } + + private: + Size old_size_; + Size new_size_; + }; + + class FocusChangeEventArgs : public UiEventArgs + { + public: + FocusChangeEventArgs(Object* sender, Object* original_sender, const bool is_window = false) + : UiEventArgs(sender, original_sender), is_window_(is_window) + { + + } + FocusChangeEventArgs(const FocusChangeEventArgs& other) = default; + FocusChangeEventArgs(FocusChangeEventArgs&& other) = default; + FocusChangeEventArgs& operator=(const FocusChangeEventArgs& other) = default; + FocusChangeEventArgs& operator=(FocusChangeEventArgs&& other) = default; + ~FocusChangeEventArgs() override = default; + + // Return whether the focus change is caused by the window-wide focus change. + bool IsWindow() const + { + return is_window_; + } + + private: + bool is_window_; + }; + + class ToggleEventArgs : public UiEventArgs + { + public: + ToggleEventArgs(Object* sender, Object* original_sender, bool new_state) + : UiEventArgs(sender, original_sender), new_state_(new_state) + { + + } + ToggleEventArgs(const ToggleEventArgs& other) = default; + ToggleEventArgs(ToggleEventArgs&& other) = default; + ToggleEventArgs& operator=(const ToggleEventArgs& other) = default; + ToggleEventArgs& operator=(ToggleEventArgs&& other) = default; + ~ToggleEventArgs() override = default; + + bool GetNewState() const + { + return new_state_; + } + + private: + bool new_state_; + }; + + struct WindowNativeMessage + { + HWND hwnd; + int msg; + WPARAM w_param; + LPARAM l_param; + }; + + class WindowNativeMessageEventArgs : public UiEventArgs + { + public: + WindowNativeMessageEventArgs(Object* sender, Object* original_sender, const WindowNativeMessage& message) + : UiEventArgs(sender, original_sender), message_(message), result_(std::nullopt) + { + + } + WindowNativeMessageEventArgs(const WindowNativeMessageEventArgs& other) = default; + WindowNativeMessageEventArgs(WindowNativeMessageEventArgs&& other) = default; + WindowNativeMessageEventArgs& operator=(const WindowNativeMessageEventArgs& other) = default; + WindowNativeMessageEventArgs& operator=(WindowNativeMessageEventArgs&& other) = default; + ~WindowNativeMessageEventArgs() override = default; + + WindowNativeMessage GetWindowMessage() const + { + return message_; + } + + std::optional<LRESULT> GetResult() const + { + return result_; + } + + void SetResult(const std::optional<LRESULT> result) + { + result_ = result; + } + + private: + WindowNativeMessage message_; + std::optional<LRESULT> result_; + }; + + class KeyEventArgs : public UiEventArgs + { + public: + KeyEventArgs(Object* sender, Object* original_sender, int virtual_code) + : UiEventArgs(sender, original_sender), virtual_code_(virtual_code) + { + } + KeyEventArgs(const KeyEventArgs& other) = default; + KeyEventArgs(KeyEventArgs&& other) = default; + KeyEventArgs& operator=(const KeyEventArgs& other) = default; + KeyEventArgs& operator=(KeyEventArgs&& other) = default; + ~KeyEventArgs() override = default; + + int GetVirtualCode() const + { + return virtual_code_; + } + + private: + int virtual_code_; + }; + + class CharEventArgs : public UiEventArgs + { + public: + CharEventArgs(Object* sender, Object* original_sender, wchar_t c) + : UiEventArgs(sender, original_sender), c_(c) + { + } + CharEventArgs(const CharEventArgs& other) = default; + CharEventArgs(CharEventArgs&& other) = default; + CharEventArgs& operator=(const CharEventArgs& other) = default; + CharEventArgs& operator=(CharEventArgs&& other) = default; + ~CharEventArgs() override = default; + + wchar_t GetChar() const + { + return c_; + } + + private: + wchar_t c_; + }; + + using UiEvent = Event<UiEventArgs>; + using MouseEvent = Event<MouseEventArgs>; + using MouseButtonEvent = Event<MouseButtonEventArgs>; + using DrawEvent = Event<DrawEventArgs>; + using PositionChangedEvent = Event<PositionChangedEventArgs>; + using SizeChangedEvent = Event<SizeChangedEventArgs>; + using FocusChangeEvent = Event<FocusChangeEventArgs>; + using ToggleEvent = Event<ToggleEventArgs>; + using WindowNativeMessageEvent = Event<WindowNativeMessageEventArgs>; + using KeyEvent = Event<KeyEventArgs>; + using CharEvent = Event<CharEventArgs>; +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\ui\events\ui_event.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\ui\border_property.hpp +//-------------------------------------------------------- + + + + +namespace cru::ui +{ + class BorderProperty final + { + public: + BorderProperty(); + explicit BorderProperty(Microsoft::WRL::ComPtr<ID2D1Brush> brush); + BorderProperty(Microsoft::WRL::ComPtr<ID2D1Brush> brush, float width, float radius_x, float radius_y, Microsoft::WRL::ComPtr<ID2D1StrokeStyle> stroke_style = nullptr); + BorderProperty(const BorderProperty& other) = default; + BorderProperty(BorderProperty&& other) = default; + BorderProperty& operator=(const BorderProperty& other) = default; + BorderProperty& operator=(BorderProperty&& other) = default; + ~BorderProperty() = default; + + + Microsoft::WRL::ComPtr<ID2D1Brush> GetBrush() const + { + return brush_; + } + + float GetStrokeWidth() const + { + return stroke_width_; + } + + Microsoft::WRL::ComPtr<ID2D1StrokeStyle> GetStrokeStyle() const + { + return stroke_style_; + } + + float GetRadiusX() const + { + return radius_x_; + } + + float GetRadiusY() const + { + return radius_y_; + } + + void SetBrush(Microsoft::WRL::ComPtr<ID2D1Brush> brush) + { + Require(brush == nullptr, "Brush of BorderProperty mustn't be null."); + brush_ = std::move(brush); + } + + void SetStrokeWidth(const float stroke_width) + { + Require(stroke_width >= 0.0f, "Stroke width must be no less than 0."); + stroke_width_ = stroke_width; + } + + void SetStrokeStyle(Microsoft::WRL::ComPtr<ID2D1StrokeStyle> stroke_style) + { + stroke_style_ = std::move(stroke_style); + } + + void SetRadiusX(const float radius_x) + { + Require(radius_x >= 0.0f, "Radius-x must be no less than 0."); + radius_x_ = radius_x; + } + + void SetRadiusY(const float radius_y) + { + Require(radius_y >= 0.0f, "Radius-y must be no less than 0."); + radius_y_ = radius_y; + } + + private: + Microsoft::WRL::ComPtr<ID2D1Brush> brush_; + float stroke_width_ = 1.0f; + float radius_x_ = 0.0f; + float radius_y_ = 0.0f; + Microsoft::WRL::ComPtr<ID2D1StrokeStyle> stroke_style_ = nullptr; + }; +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\ui\border_property.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\ui\cursor.hpp +//-------------------------------------------------------- + +#include <memory> + + +namespace cru::ui +{ + class Cursor : public Object + { + public: + using Ptr = std::shared_ptr<Cursor>; + + Cursor(HCURSOR handle, bool auto_release); + Cursor(const Cursor& other) = delete; + Cursor(Cursor&& other) = delete; + Cursor& operator=(const Cursor& other) = delete; + Cursor& operator=(Cursor&& other) = delete; + ~Cursor() override; + + HCURSOR GetHandle() const + { + return handle_; + } + + private: + HCURSOR handle_; + bool auto_release_; + }; + + namespace cursors + { + extern Cursor::Ptr arrow; + extern Cursor::Ptr hand; + extern Cursor::Ptr i_beam; + } +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\ui\cursor.hpp +//-------------------------------------------------------- + +namespace cru::ui +{ + class Control; + class Window; + + + //the position cache + struct ControlPositionCache + { + //The lefttop relative to the ancestor. + Point lefttop_position_absolute; + }; + + class Control : public Object + { + friend class Window; + friend class LayoutManager; + + protected: + struct WindowConstructorTag {}; //Used for constructor for class Window. + + explicit Control(bool container = false); + + // Used only for creating Window. It will set window_ as window. + Control(WindowConstructorTag, Window* window); + + public: + Control(const Control& other) = delete; + Control(Control&& other) = delete; + Control& operator=(const Control& other) = delete; + Control& operator=(Control&& other) = delete; + ~Control() override; + + public: + + //*************** region: tree *************** + virtual StringView GetControlType() const = 0; + + bool IsContainer() const + { + return is_container_; + } + + //Get parent of control, return nullptr if it has no parent. + Control* GetParent() const + { + return parent_; + } + + //Return a immutable vector of all children. + const std::vector<Control*>& GetChildren() const; + + //Add a child at tail. + void AddChild(Control* control); + + //Add a child before the position. + void AddChild(Control* control, int position); + + //Remove a child. + void RemoveChild(Control* child); + + //Remove a child at specified position. + void RemoveChild(int position); + + //Get the ancestor of the control. + Control* GetAncestor(); + + //Get the window if attached, otherwise, return nullptr. + Window* GetWindow() const + { + return window_; + } + + //Traverse the tree rooted the control including itself. + void TraverseDescendants(const std::function<void(Control*)>& predicate); + + //*************** region: position and size *************** + // Position and size part must be isolated from layout part. + // All the operations in this part must be done independently. + // And layout part must use api of this part. + + //Get the lefttop relative to its parent. + virtual Point GetPositionRelative(); + + //Set the lefttop relative to its parent. + virtual void SetPositionRelative(const Point& position); + + //Get the actual size. + virtual Size GetSize(); + + //Set the actual size directly without re-layout. + virtual void SetSize(const Size& size); + + //Get lefttop relative to ancestor. This is only valid when + //attached to window. Notice that the value is cached. + //You can invalidate and recalculate it by calling "InvalidatePositionCache". + Point GetPositionAbsolute() const; + + //Local point to absolute point. + Point ControlToWindow(const Point& point) const; + + //Absolute point to local point. + Point WindowToControl(const Point& point) const; + + virtual bool IsPointInside(const Point& point); + + + //*************** region: graphic *************** + + //Draw this control and its child controls. + void Draw(ID2D1DeviceContext* device_context); + + virtual void Repaint(); + + //*************** region: focus *************** + + bool RequestFocus(); + + bool HasFocus(); + + bool IsFocusOnPressed() const + { + return is_focus_on_pressed_; + } + + void SetFocusOnPressed(const bool value) + { + is_focus_on_pressed_ = value; + } + + //*************** region: layout *************** + + void InvalidateLayout(); + + void Measure(const Size& available_size); + + void Layout(const Rect& rect); + + Size GetDesiredSize() const; + + void SetDesiredSize(const Size& desired_size); + + BasicLayoutParams* GetLayoutParams() + { + return &layout_params_; + } + + Rect GetRect(RectRange range); + + Point TransformPoint(const Point& point, RectRange from = RectRange::Margin, RectRange to = RectRange::Content); + + //*************** region: border *************** + + BorderProperty& GetBorderProperty() + { + return border_property_; + } + + void InvalidateBorder(); + + bool IsBordered() const + { + return is_bordered_; + } + + void SetBordered(bool bordered); + + + //*************** region: additional properties *************** + AnyMap* GetAdditionalPropertyMap() + { + return &additional_property_map_; + } + + + //*************** region: cursor *************** + // If cursor is set to null, then it uses parent's cursor. + // Window's cursor can't be null. + + Cursor::Ptr GetCursor() const + { + return cursor_; + } + + void SetCursor(const Cursor::Ptr& cursor); + + + //*************** region: events *************** + //Raised when mouse enter the control. + events::MouseEvent mouse_enter_event; + //Raised when mouse is leave the control. + events::MouseEvent mouse_leave_event; + //Raised when mouse is move in the control. + events::MouseEvent mouse_move_event; + //Raised when a mouse button is pressed in the control. + events::MouseButtonEvent mouse_down_event; + //Raised when a mouse button is released in the control. + events::MouseButtonEvent mouse_up_event; + //Raised when a mouse button is pressed in the control and released in the control with mouse not leaving it between two operations. + events::MouseButtonEvent mouse_click_event; + + events::KeyEvent key_down_event; + events::KeyEvent key_up_event; + events::CharEvent char_event; + + events::FocusChangeEvent get_focus_event; + events::FocusChangeEvent lose_focus_event; + + events::DrawEvent draw_content_event; + events::DrawEvent draw_background_event; + events::DrawEvent draw_foreground_event; + + events::PositionChangedEvent position_changed_event; + events::SizeChangedEvent size_changed_event; + + protected: + //Invoked when a child is added. Overrides should invoke base. + virtual void OnAddChild(Control* child); + //Invoked when a child is removed. Overrides should invoke base. + virtual void OnRemoveChild(Control* child); + + //Invoked when the control is attached to a window. Overrides should invoke base. + virtual void OnAttachToWindow(Window* window); + //Invoked when the control is detached to a window. Overrides should invoke base. + virtual void OnDetachToWindow(Window* window); + + private: + void OnDrawCore(ID2D1DeviceContext* device_context); + + protected: + virtual void OnDrawContent(ID2D1DeviceContext* device_context); + virtual void OnDrawForeground(ID2D1DeviceContext* device_context); + virtual void OnDrawBackground(ID2D1DeviceContext* device_context); + + // For a event, the window event system will first dispatch event to core functions. + // Therefore for particular controls, you should do essential actions in core functions, + // and override version should invoke base version. The base core function + // in "Control" class will call corresponding non-core function and call "Raise" on + // event objects. So user custom actions should be done by overriding non-core function + // and calling the base version is optional. + + //*************** region: position and size event *************** + virtual void OnPositionChanged(events::PositionChangedEventArgs& args); + virtual void OnSizeChanged(events::SizeChangedEventArgs& args); + + virtual void OnPositionChangedCore(events::PositionChangedEventArgs& args); + virtual void OnSizeChangedCore(events::SizeChangedEventArgs& args); + + void RaisePositionChangedEvent(events::PositionChangedEventArgs& args); + void RaiseSizeChangedEvent(events::SizeChangedEventArgs& args); + + //*************** region: mouse event *************** + virtual void OnMouseEnter(events::MouseEventArgs& args); + virtual void OnMouseLeave(events::MouseEventArgs& args); + virtual void OnMouseMove(events::MouseEventArgs& args); + virtual void OnMouseDown(events::MouseButtonEventArgs& args); + virtual void OnMouseUp(events::MouseButtonEventArgs& args); + virtual void OnMouseClick(events::MouseButtonEventArgs& args); + + virtual void OnMouseEnterCore(events::MouseEventArgs& args); + virtual void OnMouseLeaveCore(events::MouseEventArgs& args); + virtual void OnMouseMoveCore(events::MouseEventArgs& args); + virtual void OnMouseDownCore(events::MouseButtonEventArgs& args); + virtual void OnMouseUpCore(events::MouseButtonEventArgs& args); + virtual void OnMouseClickCore(events::MouseButtonEventArgs& args); + + void RaiseMouseEnterEvent(events::MouseEventArgs& args); + void RaiseMouseLeaveEvent(events::MouseEventArgs& args); + void RaiseMouseMoveEvent(events::MouseEventArgs& args); + void RaiseMouseDownEvent(events::MouseButtonEventArgs& args); + void RaiseMouseUpEvent(events::MouseButtonEventArgs& args); + void RaiseMouseClickEvent(events::MouseButtonEventArgs& args); + + virtual void OnMouseClickBegin(MouseButton button); + virtual void OnMouseClickEnd(MouseButton button); + + //*************** region: keyboard event *************** + virtual void OnKeyDown(events::KeyEventArgs& args); + virtual void OnKeyUp(events::KeyEventArgs& args); + virtual void OnChar(events::CharEventArgs& args); + + virtual void OnKeyDownCore(events::KeyEventArgs& args); + virtual void OnKeyUpCore(events::KeyEventArgs& args); + virtual void OnCharCore(events::CharEventArgs& args); + + void RaiseKeyDownEvent(events::KeyEventArgs& args); + void RaiseKeyUpEvent(events::KeyEventArgs& args); + void RaiseCharEvent(events::CharEventArgs& args); + + //*************** region: focus event *************** + virtual void OnGetFocus(events::FocusChangeEventArgs& args); + virtual void OnLoseFocus(events::FocusChangeEventArgs& args); + + virtual void OnGetFocusCore(events::FocusChangeEventArgs& args); + virtual void OnLoseFocusCore(events::FocusChangeEventArgs& args); + + void RaiseGetFocusEvent(events::FocusChangeEventArgs& args); + void RaiseLoseFocusEvent(events::FocusChangeEventArgs& args); + + //*************** region: layout *************** + Size OnMeasureCore(const Size& available_size); + void OnLayoutCore(const Rect& rect); + + virtual Size OnMeasureContent(const Size& available_size); + virtual void OnLayoutContent(const Rect& rect); + + private: + // Only for layout manager to use. + // Check if the old position is updated to current position. + // If not, then a notify of position change and update will + // be done. + void CheckAndNotifyPositionChanged(); + + void ThrowIfNotContainer() const + { + if (!is_container_) + throw std::runtime_error("You can't perform such operation on a non-container control."); + } + + private: + bool is_container_; + + protected: + Window * window_ = nullptr; // protected for Window class to write it as itself in constructor. + + private: + Control * parent_ = nullptr; + std::vector<Control*> children_{}; + + // When position is changed and notification hasn't been + // sent, it will be the old position. When position is changed + // more than once, it will be the oldest position since last + // notification. If notification has been sent, it will be updated + // to position_. + Point old_position_ = Point::Zero(); + Point position_ = Point::Zero(); + Size size_ = Size::Zero(); + + ControlPositionCache position_cache_{}; + + bool is_mouse_inside_ = false; + + std::unordered_map<MouseButton, bool> is_mouse_click_valid_map_ + { + { MouseButton::Left, true }, + { MouseButton::Middle, true }, + { MouseButton::Right, true } + }; // used for clicking determination + + BasicLayoutParams layout_params_{}; + Size desired_size_ = Size::Zero(); + + bool is_bordered_ = false; + BorderProperty border_property_; + + AnyMap additional_property_map_{}; + + bool is_focus_on_pressed_ = true; + +#ifdef CRU_DEBUG_LAYOUT + Microsoft::WRL::ComPtr<ID2D1Geometry> margin_geometry_; + Microsoft::WRL::ComPtr<ID2D1Geometry> padding_geometry_; +#endif + + Cursor::Ptr cursor_{}; + }; + + // Find the lowest common ancestor. + // Return nullptr if "left" and "right" are not in the same tree. + Control* FindLowestCommonAncestor(Control* left, Control* right); + + // Return the ancestor if one control is the ancestor of the other one, otherwise nullptr. + Control* IsAncestorOrDescendant(Control* left, Control* right); + + template <typename TControl, typename... Args> + TControl* CreateWithLayout(const LayoutSideParams& width, const LayoutSideParams& height, Args&&... args) + { + static_assert(std::is_base_of_v<Control, TControl>, "TControl is not a control class."); + TControl* control = TControl::Create(std::forward<Args>(args)...); + control->GetLayoutParams()->width = width; + control->GetLayoutParams()->height = height; + return control; + } + + + template <typename TControl, typename... Args> + TControl* CreateWithLayout(const Thickness& padding, const Thickness& margin, Args&&... args) + { + static_assert(std::is_base_of_v<Control, TControl>, "TControl is not a control class."); + TControl* control = TControl::Create(std::forward<Args>(args)...); + control->GetLayoutParams()->padding = padding; + control->GetLayoutParams()->margin = margin; + return control; + } + + template <typename TControl, typename... Args> + TControl* CreateWithLayout(const LayoutSideParams& width, const LayoutSideParams& height, const Thickness& padding, const Thickness& margin, Args&&... args) + { + static_assert(std::is_base_of_v<Control, TControl>, "TControl is not a control class."); + TControl* control = TControl::Create(std::forward<Args>(args)...); + control->GetLayoutParams()->width = width; + control->GetLayoutParams()->height = height; + control->GetLayoutParams()->padding = padding; + control->GetLayoutParams()->margin = margin; + return control; + } + + using ControlList = std::initializer_list<Control*>; +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\ui\control.hpp +//-------------------------------------------------------- + +namespace cru::graph +{ + class WindowRenderTarget; +} + +namespace cru::ui +{ + class WindowClass : public Object + { + public: + WindowClass(const String& name, WNDPROC window_proc, HINSTANCE h_instance); + WindowClass(const WindowClass& other) = delete; + WindowClass(WindowClass&& other) = delete; + WindowClass& operator=(const WindowClass& other) = delete; + WindowClass& operator=(WindowClass&& other) = delete; + ~WindowClass() override = default; + + + const wchar_t* GetName() const + { + return name_.c_str(); + } + + ATOM GetAtom() const + { + return atom_; + } + + private: + String name_; + ATOM atom_; + }; + + class WindowManager : public Object + { + public: + static WindowManager* GetInstance(); + private: + WindowManager(); + public: + WindowManager(const WindowManager& other) = delete; + WindowManager(WindowManager&& other) = delete; + WindowManager& operator=(const WindowManager& other) = delete; + WindowManager& operator=(WindowManager&& other) = delete; + ~WindowManager() override = default; + + + //Get the general window class for creating ordinary window. + WindowClass* GetGeneralWindowClass() const + { + return general_window_class_.get(); + } + + //Register a window newly created. + //This function adds the hwnd to hwnd-window map. + //It should be called immediately after a window was created. + void RegisterWindow(HWND hwnd, Window* window); + + //Unregister a window that is going to be destroyed. + //This function removes the hwnd from the hwnd-window map. + //It should be called immediately before a window is going to be destroyed, + void UnregisterWindow(HWND hwnd); + + //Return a pointer to the Window object related to the HWND or nullptr if the hwnd is not in the map. + Window* FromHandle(HWND hwnd); + + std::vector<Window*> GetAllWindows() const; + + private: + std::unique_ptr<WindowClass> general_window_class_; + std::map<HWND, Window*> window_map_; + }; + + + + class Window final : public Control + { + friend class WindowManager; + public: + static constexpr auto control_type = L"Window"; + + public: + static Window* CreateOverlapped(); + static Window* CreatePopup(Window* parent, bool caption = false); + + private: + struct tag_overlapped_constructor {}; + struct tag_popup_constructor {}; + + explicit Window(tag_overlapped_constructor); + Window(tag_popup_constructor, Window* parent, bool caption); + + void AfterCreateHwnd(WindowManager* window_manager); + + public: + Window(const Window& other) = delete; + Window(Window&& other) = delete; + Window& operator=(const Window& other) = delete; + Window& operator=(Window&& other) = delete; + ~Window() override; + + public: + StringView GetControlType() const override final; + + void SetDeleteThisOnDestroy(bool value); + + //*************** region: handle *************** + + //Get the handle of the window. Return null if window is invalid. + HWND GetWindowHandle() const + { + return hwnd_; + } + + //Return if the window is still valid, that is, hasn't been closed or destroyed. + bool IsWindowValid() const + { + return hwnd_ != nullptr; + } + + + //*************** region: window operations *************** + + Window* GetParentWindow() const + { + return parent_window_; + } + + //Close and destroy the window if the window is valid. + void Close(); + + //Send a repaint message to the window's message queue which may make the window repaint. + void Repaint() override; + + //Show the window. + void Show(); + + //Hide thw window. + void Hide(); + + //Get the client size. + Size GetClientSize(); + + //Set the client size and repaint. + void SetClientSize(const Size& size); + + //Get the rect of the window containing frame. + //The lefttop of the rect is relative to screen lefttop. + Rect GetWindowRect(); + + //Set the rect of the window containing frame. + //The lefttop of the rect is relative to screen lefttop. + void SetWindowRect(const Rect& rect); + + //Set the lefttop of the window relative to screen. + void SetWindowPosition(const Point& position); + + Point PointToScreen(const Point& point); + + Point PointFromScreen(const Point& point); + + //Handle the raw window message. + //Return true if the message is handled and get the result through "result" argument. + //Return false if the message is not handled. + bool HandleWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param, LRESULT& result); + + //*************** region: mouse *************** + + Point GetMousePosition(); + + Control* GetMouseHoverControl() const + { + return mouse_hover_control_; + } + + //*************** region: position and size *************** + + //Always return (0, 0) for a window. + Point GetPositionRelative() override final; + + //This method has no effect for a window. + void SetPositionRelative(const Point& position) override final; + + //Get the size of client area for a window. + Size GetSize() override final; + + //This method has no effect for a window. Use SetClientSize instead. + void SetSize(const Size& size) override final; + + //*************** region: layout *************** + + void WindowInvalidateLayout(); + + void Relayout(); + + void SetSizeFitContent(const Size& max_size = Size(1000, 1000)); + + //*************** region: functions *************** + + //Refresh control list. + //It should be invoked every time a control is added or removed from the tree. + void RefreshControlList(); + + //Get the most top control at "point". + Control* HitTest(const Point& point); + + + //*************** region: focus *************** + + //Request focus for specified control. + bool RequestFocusFor(Control* control); + + //Get the control that has focus. + Control* GetFocusControl(); + + + //*************** region: mouse capture *************** + + Control* CaptureMouseFor(Control* control); + Control* ReleaseCurrentMouseCapture(); + + + //*************** region: cursor *************** + void UpdateCursor(); + + //*************** region: debug *************** +#ifdef CRU_DEBUG_LAYOUT + bool IsDebugLayout() const + { + return debug_layout_; + } + + void SetDebugLayout(bool value); +#endif + + public: + //*************** region: events *************** + events::UiEvent activated_event; + events::UiEvent deactivated_event; + + events::WindowNativeMessageEvent native_message_event; + + private: + //*************** region: native operations *************** + + //Get the client rect in pixel. + RECT GetClientRectPixel(); + + bool IsMessageInQueue(UINT message); + + void SetCursorInternal(HCURSOR cursor); + + + //*************** 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(MouseButton button, POINT point); + void OnMouseUpInternal(MouseButton button, POINT point); + + void OnKeyDownInternal(int virtual_code); + void OnKeyUpInternal(int virtual_code); + void OnCharInternal(wchar_t c); + + void OnActivatedInternal(); + void OnDeactivatedInternal(); + + //*************** region: event dispatcher helper *************** + + template<typename EventArgs> + using EventMethod = void (Control::*)(EventArgs&); + + // Dispatch the event. + // + // This will invoke the "event_method" of the control and its parent and parent's + // parent ... (until "last_receiver" if it's not nullptr) with appropriate args. + // + // Args is of type "EventArgs". The first init argument is "sender", which is + // automatically bound to each receiving control. The second init argument is + // "original_sender", which is unchanged. And "args" will be perfectly forwarded + // as the rest arguments. + template<typename EventArgs, typename... Args> + void DispatchEvent(Control* original_sender, EventMethod<EventArgs> event_method, Control* last_receiver, Args&&... args) + { + auto control = original_sender; + while (control != nullptr && control != last_receiver) + { + EventArgs event_args(control, original_sender, std::forward<Args>(args)...); + (control->*event_method)(event_args); + control = control->GetParent(); + } + } + + void DispatchMouseHoverControlChangeEvent(Control* old_control, Control * new_control, const Point& point); + + private: + bool delete_this_on_destroy_ = true; + + HWND hwnd_ = nullptr; + Window* parent_window_ = nullptr; + std::shared_ptr<graph::WindowRenderTarget> render_target_{}; + + std::list<Control*> control_list_{}; + + Control* mouse_hover_control_ = nullptr; + + bool window_focus_ = false; + Control* focus_control_ = this; // "focus_control_" can't be nullptr + + Control* mouse_capture_control_ = nullptr; + + bool is_layout_invalid_ = false; + +#ifdef CRU_DEBUG_LAYOUT + bool debug_layout_ = false; +#endif + }; +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\ui\window.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\cru_debug.hpp +//-------------------------------------------------------- + +#include <functional> + + +namespace cru::debug +{ + void DebugMessage(const StringView& message); + +#ifdef CRU_DEBUG + inline void DebugTime(const std::function<void()>& action, const StringView& hint_message) + { + const auto before = std::chrono::steady_clock::now(); + action(); + const auto after = std::chrono::steady_clock::now(); + const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(after - before); + DebugMessage(Format(L"{}: {}ms.\n", hint_message, duration.count())); + } + + template<typename TReturn> + TReturn DebugTime(const std::function<TReturn()>& action, const StringView& hint_message) + { + const auto before = std::chrono::steady_clock::now(); + auto&& result = action(); + const auto after = std::chrono::steady_clock::now(); + const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(after - before); + DebugMessage(Format(L"{}: {}ms.\n", hint_message, duration.count())); + return std::move(result); + } +#else + inline void DebugTime(const std::function<void()>& action, const StringView& hint_message) + { + action(); + } + + template<typename TReturn> + TReturn DebugTime(const std::function<TReturn()>& action, const StringView& hint_message) + { + return action(); + } +#endif +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\cru_debug.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\ui\controls\linear_layout.hpp +//-------------------------------------------------------- + + +namespace cru::ui::controls +{ + // Min length of main side in layout params is of no meaning. + // All children will layout from start and redundant length is blank. + class LinearLayout : public Control + { + public: + static constexpr auto control_type = L"LinearLayout"; + + enum class Orientation + { + Horizontal, + Vertical + }; + + static LinearLayout* Create(const Orientation orientation = Orientation::Vertical, const std::initializer_list<Control*>& children = std::initializer_list<Control*>()) + { + const auto linear_layout = new LinearLayout(orientation); + for (const auto control : children) + linear_layout->AddChild(control); + return linear_layout; + } + + protected: + explicit LinearLayout(Orientation orientation = Orientation::Vertical); + + public: + LinearLayout(const LinearLayout& other) = delete; + LinearLayout(LinearLayout&& other) = delete; + LinearLayout& operator=(const LinearLayout& other) = delete; + LinearLayout& operator=(LinearLayout&& other) = delete; + ~LinearLayout() override = default; + + StringView GetControlType() const override final; + + protected: + Size OnMeasureContent(const Size& available_size) override; + void OnLayoutContent(const Rect& rect) override; + + private: + Orientation orientation_; + }; +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\ui\controls\linear_layout.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\ui\controls\text_block.hpp +//-------------------------------------------------------- + +//-------------------------------------------------------- +//-------begin of file: ..\..\src\ui\controls\text_control.hpp +//-------------------------------------------------------- + + +namespace cru::ui::controls +{ + class TextControl : public Control + { + protected: + TextControl( + const Microsoft::WRL::ComPtr<IDWriteTextFormat>& init_text_format, + const Microsoft::WRL::ComPtr<ID2D1Brush>& init_brush + ); + public: + TextControl(const TextControl& other) = delete; + TextControl(TextControl&& other) = delete; + TextControl& operator=(const TextControl& other) = delete; + TextControl& operator=(TextControl&& other) = delete; + ~TextControl() override = default; + + String GetText() const + { + return text_; + } + + void SetText(const String& text); + + Microsoft::WRL::ComPtr<ID2D1Brush> GetBrush() const + { + return brush_; + } + + void SetBrush(const Microsoft::WRL::ComPtr<ID2D1Brush>& brush); + + Microsoft::WRL::ComPtr<IDWriteTextFormat> GetTextFormat() const + { + return text_format_; + } + + void SetTextFormat(const Microsoft::WRL::ComPtr<IDWriteTextFormat>& text_format); + + bool IsSelectable() const + { + return is_selectable_; + } + + std::optional<TextRange> GetSelectedRange() const + { + return selected_range_; + } + + void SetSelectedRange(std::optional<TextRange> text_range); + + void ClearSelection() + { + SetSelectedRange(std::nullopt); + } + + protected: + void SetSelectable(bool is_selectable); + + protected: + void OnSizeChangedCore(events::SizeChangedEventArgs& args) override final; + void OnDrawContent(ID2D1DeviceContext* device_context) override; + + void OnMouseDownCore(events::MouseButtonEventArgs& args) override final; + void OnMouseMoveCore(events::MouseEventArgs& args) override final; + void OnMouseUpCore(events::MouseButtonEventArgs& args) override final; + + void OnLoseFocusCore(events::FocusChangeEventArgs& args) override; + + Size OnMeasureContent(const Size& available_size) override; + + + virtual void RequestChangeCaretPosition(unsigned position); + + private: + void OnTextChangedCore(const String& old_text, const String& new_text); + + void RecreateTextLayout(); + + // param point is the mouse point relative to this control. + void UpdateCursor(const std::optional<Point>& point); + + private: + String text_; + + Microsoft::WRL::ComPtr<ID2D1Brush> brush_; + Microsoft::WRL::ComPtr<ID2D1Brush> selection_brush_; + Microsoft::WRL::ComPtr<IDWriteTextFormat> text_format_; + protected: + Microsoft::WRL::ComPtr<IDWriteTextLayout> text_layout_; + + private: + bool is_selectable_ = false; + + bool is_selecting_ = false; + unsigned mouse_down_position_ = 0; + std::optional<TextRange> selected_range_ = std::nullopt; + }; +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\ui\controls\text_control.hpp +//-------------------------------------------------------- + +namespace cru::ui::controls +{ + class TextBlock : public TextControl + { + public: + static constexpr auto control_type = L"TextBlock"; + + static TextBlock* Create(const String& text = L"") + { + const auto text_block = new TextBlock(); + text_block->SetText(text); + return text_block; + } + + protected: + TextBlock(); + public: + TextBlock(const TextBlock& other) = delete; + TextBlock(TextBlock&& other) = delete; + TextBlock& operator=(const TextBlock& other) = delete; + TextBlock& operator=(TextBlock&& other) = delete; + ~TextBlock() override = default; + + StringView GetControlType() const override final; + + using TextControl::SetSelectable; // Make this public. + }; +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\ui\controls\text_block.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\ui\controls\toggle_button.hpp +//-------------------------------------------------------- + + +namespace cru::ui::controls +{ + class ToggleButton : public Control + { + public: + static constexpr auto control_type = L"ToggleButton"; + + static ToggleButton* Create() + { + return new ToggleButton(); + } + + protected: + ToggleButton(); + + public: + ToggleButton(const ToggleButton& other) = delete; + ToggleButton(ToggleButton&& other) = delete; + ToggleButton& operator=(const ToggleButton& other) = delete; + ToggleButton& operator=(ToggleButton&& other) = delete; + ~ToggleButton() override = default; + + StringView GetControlType() const override final; + + bool IsPointInside(const Point& point) override; + + bool GetState() const + { + return state_; + } + + void SetState(bool state); + + void Toggle(); + + public: + events::ToggleEvent toggle_event; + + protected: + virtual void OnToggle(events::ToggleEventArgs& args); + + protected: + void OnDrawContent(ID2D1DeviceContext* device_context) override; + + void OnMouseClickCore(events::MouseButtonEventArgs& args) override; + + Size OnMeasureContent(const Size& available_size) override; + + private: + void RaiseToggleEvent(bool new_state); + + private: + bool state_ = false; + + float current_circle_position_; + + Microsoft::WRL::ComPtr<ID2D1RoundedRectangleGeometry> frame_path_; + Microsoft::WRL::ComPtr<ID2D1Brush> on_brush_; + Microsoft::WRL::ComPtr<ID2D1Brush> off_brush_; + }; +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\ui\controls\toggle_button.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\ui\controls\button.hpp +//-------------------------------------------------------- + +#include <initializer_list> + + +namespace cru::ui::controls +{ + class Button : public Control + { + public: + static constexpr auto control_type = L"Button"; + + static Button* Create(const std::initializer_list<Control*>& children = std::initializer_list<Control*>()) + { + const auto button = new Button(); + for (const auto control : children) + button->AddChild(control); + return 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 = default; + + StringView GetControlType() const override final; + + protected: + void OnMouseClickBegin(MouseButton button) override final; + void OnMouseClickEnd(MouseButton button) override final; + + private: + BorderProperty normal_border_; + BorderProperty pressed_border_; + }; +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\ui\controls\button.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\ui\controls\text_box.hpp +//-------------------------------------------------------- + + +namespace cru::ui::controls +{ + class TextBox : public TextControl + { + public: + static constexpr auto control_type = L"TextBox"; + + static TextBox* Create() + { + return new TextBox(); + } + + protected: + TextBox(); + public: + TextBox(const TextBox& other) = delete; + TextBox(TextBox&& other) = delete; + TextBox& operator=(const TextBox& other) = delete; + TextBox& operator=(TextBox&& other) = delete; + ~TextBox() override; + + StringView GetControlType() const override final; + + protected: + void OnDrawContent(ID2D1DeviceContext* device_context) override; + + void OnGetFocusCore(events::FocusChangeEventArgs& args) override final; + void OnLoseFocusCore(events::FocusChangeEventArgs& args) override final; + + void OnKeyDownCore(events::KeyEventArgs& args) override final; + void OnCharCore(events::CharEventArgs& args) override final; + + void RequestChangeCaretPosition(unsigned position) override final; + + private: + // return true if left + bool GetCaretSelectionSide() const; + void ShiftLeftSelectionRange(int count); + void ShiftRightSelectionRange(int count); + + private: + unsigned caret_position_ = 0; + std::optional<TimerTask> caret_timer_{}; + Microsoft::WRL::ComPtr<ID2D1Brush> caret_brush_; + bool is_caret_show_ = false; + }; +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\ui\controls\text_box.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\ui\controls\list_item.hpp +//-------------------------------------------------------- + +#include <map> +#include <initializer_list> + + +namespace cru::ui::controls +{ + class ListItem : public Control + { + public: + static constexpr auto control_type = L"ListItem"; + + enum class State + { + Normal, + Hover, + Select + }; + + private: + struct StateBrush + { + Microsoft::WRL::ComPtr<ID2D1Brush> border_brush; + Microsoft::WRL::ComPtr<ID2D1Brush> fill_brush; + }; + + public: + static ListItem* Create(const std::initializer_list<Control*>& children) + { + const auto list_item = new ListItem(); + for (auto control : children) + list_item->AddChild(control); + return list_item; + } + + private: + ListItem(); + public: + ListItem(const ListItem& other) = delete; + ListItem(ListItem&& other) = delete; + ListItem& operator=(const ListItem& other) = delete; + ListItem& operator=(ListItem&& other) = delete; + ~ListItem() override = default; + + StringView GetControlType() const override; + + State GetState() const + { + return state_; + } + + void SetState(State state); + + protected: + void OnDrawForeground(ID2D1DeviceContext* device_context) override; + + void OnMouseEnterCore(events::MouseEventArgs& args) override final; + void OnMouseLeaveCore(events::MouseEventArgs& args) override final; + void OnMouseClickCore(events::MouseButtonEventArgs& args) override final; + + private: + State state_ = State::Normal; + std::map<State, StateBrush> brushes_{}; + }; +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\ui\controls\list_item.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\ui\controls\popup_menu.hpp +//-------------------------------------------------------- + +#include <vector> +#include <utility> +#include <functional> + + +namespace cru::ui +{ + class Window; +} + +namespace cru::ui::controls +{ + using MenuItemInfo = std::pair<String, std::function<void()>>; + + Window* CreatePopupMenu(const Point& anchor, const std::vector<MenuItemInfo>& items, Window* parent = nullptr); +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\ui\controls\popup_menu.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\graph\graph.hpp +//-------------------------------------------------------- + +#include <memory> +#include <functional> + + + +namespace cru::graph +{ + class GraphManager; + + //Represents a window render target. + class WindowRenderTarget : public Object + { + public: + WindowRenderTarget(GraphManager* graph_manager, HWND hwnd); + WindowRenderTarget(const WindowRenderTarget& other) = delete; + WindowRenderTarget(WindowRenderTarget&& other) = delete; + WindowRenderTarget& operator=(const WindowRenderTarget& other) = delete; + WindowRenderTarget& operator=(WindowRenderTarget&& other) = delete; + ~WindowRenderTarget() override; + + public: + //Get the graph manager that created the render target. + GraphManager* GetGraphManager() const + { + return graph_manager_; + } + + //Get the d2d device context. + inline Microsoft::WRL::ComPtr<ID2D1DeviceContext> GetD2DDeviceContext() const; + + //Get the target bitmap which can be set as the ID2D1DeviceContext's target. + Microsoft::WRL::ComPtr<ID2D1Bitmap1> GetTargetBitmap() const + { + return target_bitmap_; + } + + //Resize the underlying buffer. + void ResizeBuffer(int width, int height); + + //Set this render target as the d2d device context's target. + void SetAsTarget(); + + //Present the data of the underlying buffer to the window. + void Present(); + + private: + void CreateTargetBitmap(); + + private: + GraphManager* graph_manager_; + Microsoft::WRL::ComPtr<IDXGISwapChain1> dxgi_swap_chain_; + Microsoft::WRL::ComPtr<ID2D1Bitmap1> target_bitmap_; + }; + + struct Dpi + { + float x; + float y; + }; + + class GraphManager final : public Object + { + public: + static GraphManager* GetInstance(); + + private: + GraphManager(); + public: + GraphManager(const GraphManager& other) = delete; + GraphManager(GraphManager&& other) = delete; + GraphManager& operator=(const GraphManager& other) = delete; + GraphManager& operator=(GraphManager&& other) = delete; + ~GraphManager() override; + + public: + Microsoft::WRL::ComPtr<ID2D1Factory1> GetD2D1Factory() const + { + return d2d1_factory_; + } + + Microsoft::WRL::ComPtr<ID2D1DeviceContext> GetD2D1DeviceContext() const + { + return d2d1_device_context_; + } + + Microsoft::WRL::ComPtr<ID3D11Device> GetD3D11Device() const + { + return d3d11_device_; + } + + Microsoft::WRL::ComPtr<IDXGIFactory2> GetDxgiFactory() const + { + return dxgi_factory_; + } + + Microsoft::WRL::ComPtr<IDWriteFactory> GetDWriteFactory() const + { + return dwrite_factory_; + } + + + //Create a window render target with the HWND. + std::shared_ptr<WindowRenderTarget> CreateWindowRenderTarget(HWND hwnd); + + //Get the desktop dpi. + Dpi GetDpi() const; + + //Reload system metrics including desktop dpi. + void ReloadSystemMetrics(); + + Microsoft::WRL::ComPtr<IDWriteFontCollection> GetSystemFontCollection() const + { + return dwrite_system_font_collection_.Get(); + } + + private: + Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_; + Microsoft::WRL::ComPtr<ID3D11DeviceContext> d3d11_device_context_; + Microsoft::WRL::ComPtr<ID2D1Factory1> d2d1_factory_; + Microsoft::WRL::ComPtr<ID2D1Device> d2d1_device_; + Microsoft::WRL::ComPtr<ID2D1DeviceContext> d2d1_device_context_; + Microsoft::WRL::ComPtr<IDXGIFactory2> dxgi_factory_; + + Microsoft::WRL::ComPtr<IDWriteFactory> dwrite_factory_; + Microsoft::WRL::ComPtr<IDWriteFontCollection> dwrite_system_font_collection_; + }; + + inline int DipToPixelInternal(const float dip, const float dpi) + { + return static_cast<int>(dip * dpi / 96.0f); + } + + inline int DipToPixelX(const float dip_x) + { + return DipToPixelInternal(dip_x, GraphManager::GetInstance()->GetDpi().x); + } + + inline int DipToPixelY(const float dip_y) + { + return DipToPixelInternal(dip_y, GraphManager::GetInstance()->GetDpi().y); + } + + inline float DipToPixelInternal(const int pixel, const float dpi) + { + return static_cast<float>(pixel) * 96.0f / dpi; + } + + inline float PixelToDipX(const int pixel_x) + { + return DipToPixelInternal(pixel_x, GraphManager::GetInstance()->GetDpi().x); + } + + inline float PixelToDipY(const int pixel_y) + { + return DipToPixelInternal(pixel_y, GraphManager::GetInstance()->GetDpi().y); + } + + Microsoft::WRL::ComPtr<ID2D1DeviceContext> WindowRenderTarget::GetD2DDeviceContext() const + { + return graph_manager_->GetD2D1DeviceContext(); + } + + inline void WithTransform(ID2D1DeviceContext* device_context, const D2D1_MATRIX_3X2_F matrix, const std::function<void(ID2D1DeviceContext*)>& action) + { + D2D1_MATRIX_3X2_F old_transform; + device_context->GetTransform(&old_transform); + device_context->SetTransform(old_transform * matrix); + action(device_context); + device_context->SetTransform(old_transform); + } +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\graph\graph.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\ui\ui_manager.hpp +//-------------------------------------------------------- + + + +namespace cru::graph +{ + class GraphManager; +} + +namespace cru::ui +{ + struct CaretInfo + { + std::chrono::milliseconds caret_blink_duration; + float half_caret_width; + }; + + + class PredefineResources : public Object + { + public: + explicit PredefineResources(graph::GraphManager* graph_manager); + PredefineResources(const PredefineResources& other) = delete; + PredefineResources(PredefineResources&& other) = delete; + PredefineResources& operator=(const PredefineResources& other) = delete; + PredefineResources& operator=(PredefineResources&& other) = delete; + ~PredefineResources() override = default; + + //region BorderProperty + Microsoft::WRL::ComPtr<ID2D1Brush> border_property_brush; + + //region Button + BorderProperty button_normal_border; + BorderProperty button_press_border; + + //region TextControl + Microsoft::WRL::ComPtr<ID2D1Brush> text_control_selection_brush; + + //region TextBox + BorderProperty text_box_border; + Microsoft::WRL::ComPtr<ID2D1Brush> text_box_text_brush; + Microsoft::WRL::ComPtr<IDWriteTextFormat> text_box_text_format; + Microsoft::WRL::ComPtr<ID2D1Brush> text_box_caret_brush; + + //region TextBlock + Microsoft::WRL::ComPtr<ID2D1Brush> text_block_text_brush; + Microsoft::WRL::ComPtr<IDWriteTextFormat> text_block_text_format; + + //region ToggleButton + Microsoft::WRL::ComPtr<ID2D1Brush> toggle_button_on_brush; + Microsoft::WRL::ComPtr<ID2D1Brush> toggle_button_off_brush; + + //region ListItem + Microsoft::WRL::ComPtr<ID2D1Brush> list_item_normal_border_brush; + Microsoft::WRL::ComPtr<ID2D1Brush> list_item_normal_fill_brush; + Microsoft::WRL::ComPtr<ID2D1Brush> list_item_hover_border_brush; + Microsoft::WRL::ComPtr<ID2D1Brush> list_item_hover_fill_brush; + Microsoft::WRL::ComPtr<ID2D1Brush> list_item_select_border_brush; + Microsoft::WRL::ComPtr<ID2D1Brush> list_item_select_fill_brush; + + +#ifdef CRU_DEBUG_LAYOUT + //region debug + Microsoft::WRL::ComPtr<ID2D1Brush> debug_layout_out_border_brush; + Microsoft::WRL::ComPtr<ID2D1Brush> debug_layout_margin_brush; + Microsoft::WRL::ComPtr<ID2D1Brush> debug_layout_padding_brush; +#endif + }; + + class UiManager : public Object + { + public: + static UiManager* GetInstance(); + private: + UiManager(); + public: + UiManager(const UiManager& other) = delete; + UiManager(UiManager&& other) = delete; + UiManager& operator=(const UiManager& other) = delete; + UiManager& operator=(UiManager&& other) = delete; + ~UiManager() override = default; + + CaretInfo GetCaretInfo() const + { + return caret_info_; + } + + const PredefineResources* GetPredefineResources() const + { + return &predefine_resources_; + } + + private: + CaretInfo caret_info_; + + PredefineResources predefine_resources_; + }; +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\ui\ui_manager.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\ui\convert_util.hpp +//-------------------------------------------------------- + + + +namespace cru::ui +{ + inline D2D1_RECT_F Convert(const Rect& rect) + { + return D2D1::RectF(rect.left, rect.top, rect.left + rect.width, rect.top + rect.height); + } +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\ui\convert_util.hpp +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------begin of file: ..\..\src\ui\animations\animation.hpp +//-------------------------------------------------------- + +#include <unordered_map> + + +namespace cru::ui::animations +{ + using AnimationTimeUnit = FloatSecond; + + struct IAnimationDelegate : virtual Interface + { + virtual void Cancel() = 0; + }; + + using AnimationDelegatePtr = std::shared_ptr<IAnimationDelegate>; + + using AnimationStepHandler = std::function<void(AnimationDelegatePtr, double)>; + using AnimationStartHandler = std::function<void(AnimationDelegatePtr)>; + using AnimationFinishHandler = std::function<void()>; + using AnimationCancelHandler = std::function<void()>; + + namespace details + { + class Animation; + using AnimationPtr = std::unique_ptr<Animation>; + + class AnimationInfo + { + public: + AnimationInfo(String tag, const AnimationTimeUnit duration) + : tag(std::move(tag)), + duration(duration) + { + + } + AnimationInfo(const AnimationInfo& other) = default; + AnimationInfo(AnimationInfo&& other) = default; + AnimationInfo& operator=(const AnimationInfo& other) = default; + AnimationInfo& operator=(AnimationInfo&& other) = default; + ~AnimationInfo() = default; + + String tag; + AnimationTimeUnit duration; + std::vector<AnimationStepHandler> step_handlers{}; + std::vector<AnimationStartHandler> start_handlers{}; + std::vector<AnimationFinishHandler> finish_handlers{}; + std::vector<AnimationCancelHandler> cancel_handlers{}; + }; + + class AnimationManager : public Object + { + public: + static AnimationManager* GetInstance(); + private: + AnimationManager(); + public: + AnimationManager(const AnimationManager& other) = delete; + AnimationManager(AnimationManager&& other) = delete; + AnimationManager& operator=(const AnimationManager& other) = delete; + AnimationManager& operator=(AnimationManager&& other) = delete; + ~AnimationManager() override; + + AnimationDelegatePtr CreateAnimation(AnimationInfo info); + void RemoveAnimation(const String& tag); + + private: + void SetTimer(); + void KillTimer(); + + private: + std::unordered_map<String, AnimationPtr> animations_; + std::optional<TimerTask> timer_; + }; + } + + class AnimationBuilder : public Object + { + public: + AnimationBuilder(String tag, const AnimationTimeUnit duration) + : info_(std::move(tag), duration) + { + + } + + AnimationBuilder& AddStepHandler(const AnimationStepHandler& handler) + { + CheckValid(); + info_.step_handlers.push_back(handler); + return *this; + } + + AnimationBuilder& AddStartHandler(const AnimationStartHandler& handler) + { + CheckValid(); + info_.start_handlers.push_back(handler); + return *this; + } + + AnimationBuilder& AddFinishHandler(const AnimationFinishHandler& handler) + { + CheckValid(); + info_.finish_handlers.push_back(handler); + return *this; + } + + AnimationBuilder& AddCancelHandler(const AnimationCancelHandler& handler) + { + CheckValid(); + info_.cancel_handlers.push_back(handler); + return *this; + } + + AnimationDelegatePtr Start(); + + private: + void CheckValid() const + { + if (!valid_) + throw std::runtime_error("The animation builder is invalid."); + } + + bool valid_ = true; + details::AnimationInfo info_; + }; +} +//-------------------------------------------------------- +//-------end of file: ..\..\src\ui\animations\animation.hpp +//-------------------------------------------------------- |