aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CruUI/CruUI.vcxproj1
-rw-r--r--CruUI/CruUI.vcxproj.filters3
-rw-r--r--CruUI/base.cpp20
-rw-r--r--CruUI/base.h5
-rw-r--r--CruUI/main.cpp37
-rw-r--r--CruUI/ui/control.cpp13
-rw-r--r--CruUI/ui/control.h46
-rw-r--r--CruUI/ui/controls/linear_layout.cpp75
-rw-r--r--CruUI/ui/controls/linear_layout.h13
-rw-r--r--CruUI/ui/controls/text_block.cpp1
-rw-r--r--CruUI/ui/layout_base.cpp8
-rw-r--r--CruUI/ui/layout_base.h10
-rw-r--r--CruUI/ui/window.cpp19
-rw-r--r--CruUI/ui/window.h16
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, &current_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
};
}
}