diff options
-rw-r--r-- | CruUI/CruUI.vcxproj | 1 | ||||
-rw-r--r-- | CruUI/CruUI.vcxproj.filters | 3 | ||||
-rw-r--r-- | CruUI/base.cpp | 20 | ||||
-rw-r--r-- | CruUI/base.h | 5 | ||||
-rw-r--r-- | CruUI/main.cpp | 37 | ||||
-rw-r--r-- | CruUI/ui/control.cpp | 13 | ||||
-rw-r--r-- | CruUI/ui/control.h | 46 | ||||
-rw-r--r-- | CruUI/ui/controls/linear_layout.cpp | 75 | ||||
-rw-r--r-- | CruUI/ui/controls/linear_layout.h | 13 | ||||
-rw-r--r-- | CruUI/ui/controls/text_block.cpp | 1 | ||||
-rw-r--r-- | CruUI/ui/layout_base.cpp | 8 | ||||
-rw-r--r-- | CruUI/ui/layout_base.h | 10 | ||||
-rw-r--r-- | CruUI/ui/window.cpp | 19 | ||||
-rw-r--r-- | CruUI/ui/window.h | 16 |
14 files changed, 217 insertions, 50 deletions
diff --git a/CruUI/CruUI.vcxproj b/CruUI/CruUI.vcxproj index df67a297..b1402f7a 100644 --- a/CruUI/CruUI.vcxproj +++ b/CruUI/CruUI.vcxproj @@ -174,6 +174,7 @@ </ItemGroup> <ItemGroup> <ClCompile Include="application.cpp" /> + <ClCompile Include="base.cpp" /> <ClCompile Include="exception.cpp" /> <ClCompile Include="ui\animations\animation.cpp" /> <ClCompile Include="ui\layout_base.cpp" /> diff --git a/CruUI/CruUI.vcxproj.filters b/CruUI/CruUI.vcxproj.filters index a7ea8f69..b41bcb9f 100644 --- a/CruUI/CruUI.vcxproj.filters +++ b/CruUI/CruUI.vcxproj.filters @@ -113,5 +113,8 @@ <ClCompile Include="ui\animations\animation.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="base.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> </Project>
\ No newline at end of file diff --git a/CruUI/base.cpp b/CruUI/base.cpp new file mode 100644 index 00000000..c6b57e33 --- /dev/null +++ b/CruUI/base.cpp @@ -0,0 +1,20 @@ +#include "base.h" + +#include "system_headers.h" +#include "exception.h" + +namespace cru +{ + MultiByteString ToUtf8String(const StringView& string) + { + if (string.empty()) + return MultiByteString(); + + const auto length = ::WideCharToMultiByte(CP_UTF8, 0, string.data(), -1, nullptr, 0, nullptr, nullptr); + MultiByteString result; + result.reserve(length); + if (::WideCharToMultiByte(CP_UTF8, 0, string.data(), -1, result.data(), result.capacity(), nullptr, nullptr) == 0) + throw Win32Error(::GetLastError(), "Failed to convert wide string to UTF-8."); + return result; + } +} diff --git a/CruUI/base.h b/CruUI/base.h index 542546fb..7ef78014 100644 --- a/CruUI/base.h +++ b/CruUI/base.h @@ -35,11 +35,14 @@ namespace cru #ifdef CRU_DEBUG using String = std::wstring; + using MultiByteString = std::string; #else using String = folly::basic_fbstring<wchar_t>; + using MultiByteString = folly::fbstring; #endif using StringView = std::wstring_view; + using MultiByteStringView = std::string_view; template<typename FunctionType> using Function = folly::Function<FunctionType>; @@ -99,4 +102,6 @@ namespace cru }; using CancelablePtr = std::shared_ptr<ICancelable>; + + MultiByteString ToUtf8String(const StringView& string); } diff --git a/CruUI/main.cpp b/CruUI/main.cpp index e8dc60d6..6c96c833 100644 --- a/CruUI/main.cpp +++ b/CruUI/main.cpp @@ -81,7 +81,20 @@ int APIENTRY wWinMain( layout->AddChild(TextBlock::Create(L"Layout is clicked!")); }); - layout->AddChild(ToggleButton::Create()); + const auto inner_layout = LinearLayout::Create(LinearLayout::Orientation::Horizontal); + LinearLayout::SetAlignment(inner_layout, cru::ui::Alignment::End); + + layout->AddChild(inner_layout); + + inner_layout->AddChild(TextBlock::Create(L"Toggle debug border")); + + const auto toggle_button = ToggleButton::Create(); + toggle_button->toggle_event.AddHandler([&window](cru::ui::events::ToggleEventArgs& args) + { + window.SetDebugDrawControlBorder(args.GetNewState()); + }); + + inner_layout->AddChild(toggle_button); auto&& create_text_block = [](const String& text, const MeasureLength& width = MeasureLength::Content(), const MeasureLength& height = MeasureLength::Content()) { @@ -91,16 +104,24 @@ int APIENTRY wWinMain( return text_block; }; - const auto text_block = create_text_block(L"Hello World!!!", MeasureLength::Exactly(200), MeasureLength::Exactly(50)); + { + const auto text_block = create_text_block(L"Hello World!!!", MeasureLength::Exactly(200), MeasureLength::Exactly(80)); + + text_block->mouse_click_event.AddHandler([layout](cru::ui::events::MouseButtonEventArgs& args) + { + layout->AddChild(TextBlock::Create(L"Hello world is clicked!")); + }); + + layout->AddChild(text_block); + layout->AddChild(create_text_block(L"This is a very very very very very long sentence!!!", MeasureLength::Stretch(), MeasureLength::Stretch())); + } - text_block->mouse_click_event.AddHandler([layout](cru::ui::events::MouseButtonEventArgs& args) { - layout->AddChild(TextBlock::Create(L"Hello world is clicked!")); - }); + const auto text_block = TextBlock::Create(L"This is a little short sentence!!!"); + LinearLayout::SetAlignment(text_block, cru::ui::Alignment::Start); + layout->AddChild(text_block); + } - layout->AddChild(text_block); - layout->AddChild(create_text_block(L"This is a very very very very very long sentence!!!", MeasureLength::Stretch(), MeasureLength::Stretch())); - layout->AddChild(TextBlock::Create(L"This is a little short sentence!!!")); layout->AddChild(create_text_block(L"By crupest!!!", MeasureLength::Stretch(), MeasureLength::Stretch())); diff --git a/CruUI/ui/control.cpp b/CruUI/ui/control.cpp index 4d69100d..0b19c20b 100644 --- a/CruUI/ui/control.cpp +++ b/CruUI/ui/control.cpp @@ -22,12 +22,12 @@ namespace cru { size_(), position_cache_(), is_mouse_inside_(false), - is_mouse_leave_{ + is_mouse_leave_ + { { MouseButton::Left, true }, { MouseButton::Middle, true }, { MouseButton::Right, true } }, - layout_params_(new BasicLayoutParams()), desired_size_() { @@ -322,9 +322,12 @@ namespace cru { void Control::OnDraw(ID2D1DeviceContext * device_context) { #ifdef CRU_DEBUG_DRAW_CONTROL_BORDER - auto brush = Application::GetInstance()->GetDebugBorderBrush(); - const auto size = GetSize(); - device_context->DrawRectangle(D2D1::RectF(0, 0, size.width, size.height), brush.Get()); + if (GetWindow()->GetDebugDrawControlBorder()) + { + auto brush = Application::GetInstance()->GetDebugBorderBrush(); + const auto size = GetSize(); + device_context->DrawRectangle(D2D1::RectF(0, 0, size.width, size.height), brush.Get()); + } #endif } diff --git a/CruUI/ui/control.h b/CruUI/ui/control.h index 19995c76..c69486c9 100644 --- a/CruUI/ui/control.h +++ b/CruUI/ui/control.h @@ -1,8 +1,10 @@ #pragma once #include "system_headers.h" -#include <optional> #include <unordered_map> +#include <any> +#include <typeinfo> +#include <fmt/format.h> #include "base.h" #include "ui_base.h" @@ -152,19 +154,39 @@ namespace cru void SetDesiredSize(const Size& desired_size); - template<typename TLayoutParams = BasicLayoutParams> - std::shared_ptr<TLayoutParams> GetLayoutParams() + BasicLayoutParams* GetLayoutParams() { - static_assert(std::is_base_of_v<BasicLayoutParams, TLayoutParams>, "TLayoutParams must be subclass of BasicLayoutParams."); - return static_cast<std::shared_ptr<BasicLayoutParams>>(layout_params_); + return &layout_params_; } - template<typename TLayoutParams = BasicLayoutParams, - typename = std::enable_if_t<std::is_base_of_v<BasicLayoutParams, TLayoutParams>>> - void SetLayoutParams(std::shared_ptr<TLayoutParams> basic_layout_params) + //*************** region: additional properties *************** + template <typename T> + std::optional<T> GetAdditionalProperty(const String& key) { - static_assert(std::is_base_of_v<BasicLayoutParams, TLayoutParams>, "TLayoutParams must be subclass of BasicLayoutParams."); - layout_params_ = basic_layout_params; + try + { + const auto find_result = additional_properties_.find(key); + if (find_result != additional_properties_.cend()) + return std::any_cast<T>(find_result->second); + else + return std::nullopt; + } + catch (const std::bad_any_cast&) + { + throw std::runtime_error(fmt::format("Key \"{}\" is not of the type {}.", ToUtf8String(key), typeid(T).name())); + } + } + + template <typename T> + void SetAdditionalProperty(const String& key, const T& value) + { + additional_properties_[key] = std::make_any<T>(value); + } + + template <typename T> + void SetAdditionalProperty(const String& key, T&& value) + { + additional_properties_[key] = std::make_any<T>(std::move(value)); } //*************** region: events *************** @@ -294,8 +316,10 @@ namespace cru std::unordered_map<MouseButton, bool> is_mouse_leave_; // used for clicking determination - std::shared_ptr<BasicLayoutParams> layout_params_; + BasicLayoutParams layout_params_; Size desired_size_; + + std::unordered_map<String, std::any> additional_properties_; }; // Find the lowest common ancestor. diff --git a/CruUI/ui/controls/linear_layout.cpp b/CruUI/ui/controls/linear_layout.cpp index 5f55ba0a..66005b2e 100644 --- a/CruUI/ui/controls/linear_layout.cpp +++ b/CruUI/ui/controls/linear_layout.cpp @@ -8,6 +8,16 @@ namespace cru::ui::controls } + inline float AtLeast0(const float value) + { + return value < 0 ? 0 : value; + } + + inline Size AtLeast0(const Size& size) + { + return Size(AtLeast0(size.width), AtLeast0(size.height)); + } + Size LinearLayout::OnMeasure(const Size& available_size) { const auto layout_params = GetLayoutParams(); @@ -40,33 +50,35 @@ namespace cru::ui::controls auto rest_available_size_for_children = total_available_size_for_children; + float secondary_side_child_max_length = 0; + std::list<Control*> stretch_control_list; // First measure Content and Exactly and count Stretch. if (orientation_ == Orientation::Horizontal) - ForeachChild([&rest_available_size_for_children, &stretch_control_list](Control* const control) + ForeachChild([&](Control* const control) { const auto mode = control->GetLayoutParams()->width.mode; if (mode == MeasureMode::Content || mode == MeasureMode::Exactly) { - control->Measure(rest_available_size_for_children); - rest_available_size_for_children.width -= control->GetDesiredSize().width; - if (rest_available_size_for_children.width < 0) - rest_available_size_for_children.width = 0; + control->Measure(AtLeast0(rest_available_size_for_children)); + const auto size = control->GetDesiredSize(); + rest_available_size_for_children.width -= size.width; + secondary_side_child_max_length = std::max(size.height, secondary_side_child_max_length); } else stretch_control_list.push_back(control); }); else - ForeachChild([&rest_available_size_for_children, &stretch_control_list](Control* const control) + ForeachChild([&](Control* const control) { const auto mode = control->GetLayoutParams()->height.mode; if (mode == MeasureMode::Content || mode == MeasureMode::Exactly) { - control->Measure(rest_available_size_for_children); - rest_available_size_for_children.height -= control->GetDesiredSize().height; - if (rest_available_size_for_children.height < 0) - rest_available_size_for_children.height = 0; + control->Measure(AtLeast0(rest_available_size_for_children)); + const auto size = control->GetDesiredSize(); + rest_available_size_for_children.height -= size.height; + secondary_side_child_max_length = std::max(size.width, secondary_side_child_max_length); } else stretch_control_list.push_back(control); @@ -77,10 +89,10 @@ namespace cru::ui::controls const auto available_width = rest_available_size_for_children.width / stretch_control_list.size(); for (const auto control : stretch_control_list) { - control->Measure(Size(available_width, rest_available_size_for_children.height)); - rest_available_size_for_children.width -= control->GetDesiredSize().width; - if (rest_available_size_for_children.width < 0) - rest_available_size_for_children.width = 0; + control->Measure(Size(available_width, AtLeast0(rest_available_size_for_children.height))); + const auto size = control->GetDesiredSize(); + rest_available_size_for_children.width -= size.width; + secondary_side_child_max_length = std::max(size.height, secondary_side_child_max_length); } } else @@ -88,18 +100,24 @@ namespace cru::ui::controls const auto available_height = rest_available_size_for_children.height / stretch_control_list.size(); for (const auto control : stretch_control_list) { - control->Measure(Size(rest_available_size_for_children.width, available_height)); - rest_available_size_for_children.height -= control->GetDesiredSize().height; - if (rest_available_size_for_children.height < 0) - rest_available_size_for_children.height = 0; + control->Measure(Size(AtLeast0(rest_available_size_for_children.width), available_height)); + const auto size = control->GetDesiredSize(); + rest_available_size_for_children.height -= size.height; + secondary_side_child_max_length = std::max(size.width, secondary_side_child_max_length); } } auto actual_size_for_children = total_available_size_for_children; if (orientation_ == Orientation::Horizontal) + { actual_size_for_children.width -= rest_available_size_for_children.width; + actual_size_for_children.height = secondary_side_child_max_length; + } else + { + actual_size_for_children.width = secondary_side_child_max_length; actual_size_for_children.height -= rest_available_size_for_children.height; + } auto&& calculate_final_length = [](const MeasureLength& layout_length, const float length_for_children, const float max_child_length) -> float { @@ -127,14 +145,31 @@ namespace cru::ui::controls ForeachChild([this, ¤t_anchor_length, rect](Control* control) { const auto size = control->GetDesiredSize(); + const auto alignment = GetAlignment(control); + + auto&& calculate_anchor = [alignment](const float layout_length, const float control_length) -> float + { + switch (alignment) + { + case Alignment::Center: + return (layout_length - control_length) / 2; + case Alignment::Start: + return 0; + case Alignment::End: + return layout_length - control_length; + default: + UnreachableCode(); + } + }; + if (orientation_ == Orientation::Horizontal) { - control->Layout(Rect(Point(current_anchor_length, (rect.height - size.height) / 2), size)); + control->Layout(Rect(Point(current_anchor_length, calculate_anchor(rect.height, size.height)), size)); current_anchor_length += size.width; } else { - control->Layout(Rect(Point((rect.width - size.width) / 2, current_anchor_length), size)); + control->Layout(Rect(Point(calculate_anchor(rect.width, size.width), current_anchor_length), size)); current_anchor_length += size.height; } }); diff --git a/CruUI/ui/controls/linear_layout.h b/CruUI/ui/controls/linear_layout.h index ead56081..7c1fb2a8 100644 --- a/CruUI/ui/controls/linear_layout.h +++ b/CruUI/ui/controls/linear_layout.h @@ -6,7 +6,20 @@ namespace cru::ui::controls { class LinearLayout : public Control { + private: + constexpr static auto alignment_key = L"linear_layout_alignment"; + public: + static Alignment GetAlignment(Control* control) + { + return control->GetAdditionalProperty<Alignment>(alignment_key).value_or(Alignment::Center); + } + + static void SetAlignment(Control* control, Alignment alignment) + { + control->SetAdditionalProperty(alignment_key, alignment); + } + enum class Orientation { Horizontal, diff --git a/CruUI/ui/controls/text_block.cpp b/CruUI/ui/controls/text_block.cpp index 8921198b..5fabb3f4 100644 --- a/CruUI/ui/controls/text_block.cpp +++ b/CruUI/ui/controls/text_block.cpp @@ -3,7 +3,6 @@ #include "ui/window.h" #include "graph/graph.h" #include "exception.h" -#include "debug_base.h" namespace cru { diff --git a/CruUI/ui/layout_base.cpp b/CruUI/ui/layout_base.cpp index a589d2ea..a26379a0 100644 --- a/CruUI/ui/layout_base.cpp +++ b/CruUI/ui/layout_base.cpp @@ -17,11 +17,13 @@ namespace cru::ui return; // find descendant then erase it; find ancestor then just return. - for (auto i = cache_invalid_controls_.cbegin(); i != cache_invalid_controls_.cend(); ++i) + auto i = cache_invalid_controls_.cbegin(); + while (i != cache_invalid_controls_.cend()) { - const auto result = IsAncestorOrDescendant(*i, control); + auto current_i = i++; + const auto result = IsAncestorOrDescendant(*current_i, control); if (result == control) - cache_invalid_controls_.erase(i); + cache_invalid_controls_.erase(current_i); else if (result != nullptr) return; // find a ancestor of "control", just return } diff --git a/CruUI/ui/layout_base.h b/CruUI/ui/layout_base.h index 25a6774c..80a204c4 100644 --- a/CruUI/ui/layout_base.h +++ b/CruUI/ui/layout_base.h @@ -12,6 +12,12 @@ namespace cru { class Control; + enum class Alignment + { + Center, + Start, + End + }; enum class MeasureMode { @@ -61,14 +67,14 @@ namespace cru MeasureMode mode = MeasureMode::Content; }; - struct BasicLayoutParams + struct BasicLayoutParams final { BasicLayoutParams() = default; BasicLayoutParams(const BasicLayoutParams&) = default; BasicLayoutParams(BasicLayoutParams&&) = default; BasicLayoutParams& operator = (const BasicLayoutParams&) = default; BasicLayoutParams& operator = (BasicLayoutParams&&) = default; - virtual ~BasicLayoutParams() = default; + ~BasicLayoutParams() = default; bool Validate() const { diff --git a/CruUI/ui/window.cpp b/CruUI/ui/window.cpp index 926fd77a..21528d9d 100644 --- a/CruUI/ui/window.cpp +++ b/CruUI/ui/window.cpp @@ -77,6 +77,14 @@ namespace cru return find_result->second; } + Vector<Window*> WindowManager::GetAllWindows() const + { + Vector<Window*> windows; + for (auto [key, value] : window_map_) + windows.push_back(value); + return std::move(windows); + } + inline Point PiToDip(const POINT& pi_point) { return Point( @@ -401,6 +409,17 @@ namespace cru } } +#ifdef CRU_DEBUG_DRAW_CONTROL_BORDER + void Window::SetDebugDrawControlBorder(const bool value) + { + if (debug_draw_control_border_ != value) + { + debug_draw_control_border_ = value; + Repaint(); + } + } +#endif + RECT Window::GetClientRectPixel() { RECT rect{ }; GetClientRect(hwnd_, &rect); diff --git a/CruUI/ui/window.h b/CruUI/ui/window.h index 5addf963..b9e9a184 100644 --- a/CruUI/ui/window.h +++ b/CruUI/ui/window.h @@ -69,6 +69,8 @@ namespace cru { //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); + Vector<Window*> GetAllWindows() const; + private: std::unique_ptr<WindowClass> general_window_class_; std::map<HWND, Window*> window_map_; @@ -177,6 +179,16 @@ namespace cru { Control* CaptureMouseFor(Control* control); Control* ReleaseCurrentMouseCapture(); + //*************** region: debug *************** +#ifdef CRU_DEBUG_DRAW_CONTROL_BORDER + bool GetDebugDrawControlBorder() const + { + return debug_draw_control_border_; + } + + void SetDebugDrawControlBorder(bool value); +#endif + public: //*************** region: events *************** events::UiEvent activated_event; @@ -248,6 +260,10 @@ namespace cru { Control* focus_control_ = this; // "focus_control_" can't be nullptr Control* mouse_capture_control_ = nullptr; + +#ifdef CRU_DEBUG_DRAW_CONTROL_BORDER + bool debug_draw_control_border_ = false; +#endif }; } } |