aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYuqian Yang <crupest@outlook.com>2018-10-01 17:11:11 +0000
committerYuqian Yang <crupest@outlook.com>2018-10-01 17:11:11 +0000
commit30ecda8bb354d5982978af97aa90b5f49d9ea195 (patch)
treea271bddb244fa2041f14f8d46d249457cee09e5f
parent398b8f3ba535bb43c4b8593e3027c14894a7a211 (diff)
parent040a6c18f18100b825a56443a73aa1de64e4518c (diff)
downloadcru-30ecda8bb354d5982978af97aa90b5f49d9ea195.tar.gz
cru-30ecda8bb354d5982978af97aa90b5f49d9ea195.tar.bz2
cru-30ecda8bb354d5982978af97aa90b5f49d9ea195.zip
Merge branch '9-border' into 'master'
Resolve "Abstract out border control of button and border." Closes #9 See merge request crupest/CruUI!11
-rw-r--r--CruUI.vcxproj2
-rw-r--r--CruUI.vcxproj.filters6
-rw-r--r--src/base.cpp21
-rw-r--r--src/base.h29
-rw-r--r--src/main.cpp15
-rw-r--r--src/ui/control.cpp127
-rw-r--r--src/ui/control.h3
-rw-r--r--src/ui/controls/border.cpp45
-rw-r--r--src/ui/controls/border.h45
-rw-r--r--src/ui/controls/border_delegate.cpp97
-rw-r--r--src/ui/controls/border_delegate.h101
-rw-r--r--src/ui/controls/button.cpp32
-rw-r--r--src/ui/controls/button.h11
-rw-r--r--src/ui/controls/margin_container.cpp32
-rw-r--r--src/ui/controls/text_box.cpp28
-rw-r--r--src/ui/controls/text_box.h5
-rw-r--r--src/ui/controls/text_control.cpp70
-rw-r--r--src/ui/controls/text_control.h4
18 files changed, 547 insertions, 126 deletions
diff --git a/CruUI.vcxproj b/CruUI.vcxproj
index 5149d358..975d74d8 100644
--- a/CruUI.vcxproj
+++ b/CruUI.vcxproj
@@ -122,12 +122,14 @@
<ClCompile Include="src\ui\animations\animation.cpp" />
<ClCompile Include="src\ui\control.cpp" />
<ClCompile Include="src\ui\controls\border.cpp" />
+ <ClCompile Include="src\ui\controls\border_delegate.cpp" />
<ClCompile Include="src\ui\controls\button.cpp" />
<ClCompile Include="src\ui\controls\linear_layout.cpp" />
<ClCompile Include="src\ui\controls\margin_container.cpp" />
<ClCompile Include="src\ui\controls\text_block.cpp" />
<ClCompile Include="src\ui\controls\text_box.cpp" />
<ClInclude Include="src\ui\controls\border.h" />
+ <ClInclude Include="src\ui\controls\border_delegate.h" />
<ClInclude Include="src\ui\controls\text_control.h" />
<ClCompile Include="src\ui\controls\toggle_button.cpp" />
<ClCompile Include="src\ui\events\ui_event.cpp" />
diff --git a/CruUI.vcxproj.filters b/CruUI.vcxproj.filters
index 58d9f369..683b3686 100644
--- a/CruUI.vcxproj.filters
+++ b/CruUI.vcxproj.filters
@@ -72,6 +72,9 @@
<ClCompile Include="src\ui\controls\border.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="src\ui\controls\border_delegate.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\application.h">
@@ -143,6 +146,9 @@
<ClInclude Include="src\ui\controls\border.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="src\ui\controls\border_delegate.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\application.cpp">
diff --git a/src/base.cpp b/src/base.cpp
index f5868170..57a4848e 100644
--- a/src/base.cpp
+++ b/src/base.cpp
@@ -17,4 +17,25 @@ namespace cru
throw Win32Error(::GetLastError(), "Failed to convert wide string to UTF-8.");
return result;
}
+
+ void PropertyChangedNotifyObject::AddPropertyChangedListener(FunctionPtr<void(String)> listener)
+ {
+ listeners_.push_back(std::move(listener));
+ }
+
+ void PropertyChangedNotifyObject::RemovePropertyChangedListener(const FunctionPtr<void(String)>& listener)
+ {
+ for (auto i = listeners_.cbegin(); i != listeners_.cend(); ++i)
+ if (*i == listener)
+ {
+ listeners_.erase(i);
+ break;
+ }
+ }
+
+ void PropertyChangedNotifyObject::RaisePropertyChangedEvent(String property_name)
+ {
+ for (const auto& listener : listeners_)
+ (*listener)(property_name);
+ }
}
diff --git a/src/base.h b/src/base.h
index 7ef78014..18bf9cc5 100644
--- a/src/base.h
+++ b/src/base.h
@@ -20,6 +20,7 @@
#include <memory>
#include <string_view>
#include <chrono>
+#include <list>
namespace cru
{
@@ -60,6 +61,12 @@ namespace cru
return std::make_shared<typename Type::element_type>(std::forward<Args>(args)...);
}
+ template<typename Type>
+ FunctionPtr<Type> CreateFunctionPtr(Function<Type>&& function)
+ {
+ return std::make_shared<Function<Type>>(std::move(function));
+ }
+
inline ActionPtr CreateActionPtr(Action&& action)
{
return std::make_shared<Action>(std::move(action));
@@ -104,4 +111,26 @@ namespace cru
using CancelablePtr = std::shared_ptr<ICancelable>;
MultiByteString ToUtf8String(const StringView& string);
+
+
+ class PropertyChangedNotifyObject : public Object
+ {
+ public:
+ PropertyChangedNotifyObject() = default;
+ PropertyChangedNotifyObject(const PropertyChangedNotifyObject& other) = delete;
+ PropertyChangedNotifyObject(PropertyChangedNotifyObject&& other) = delete;
+ PropertyChangedNotifyObject& operator = (const PropertyChangedNotifyObject& other) = delete;
+ PropertyChangedNotifyObject& operator = (PropertyChangedNotifyObject&& other) = delete;
+ ~PropertyChangedNotifyObject() override = default;
+
+ void AddPropertyChangedListener(FunctionPtr<void(String)> listener);
+
+ void RemovePropertyChangedListener(const FunctionPtr<void(String)>& listener);
+
+ protected:
+ void RaisePropertyChangedEvent(String property_name);
+
+ private:
+ std::list<FunctionPtr<void(String)>> listeners_;
+ };
}
diff --git a/src/main.cpp b/src/main.cpp
index 4f711208..7a105d79 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -143,12 +143,27 @@ int APIENTRY wWinMain(
window.AddChild(layout);
*/
+ /*
window.AddChild(
CreateWithLayout<Border>(LayoutSideParams::Exactly(200), LayoutSideParams::Content(),
std::initializer_list<cru::ui::Control*>{
CreateWithLayout<TextBox>(LayoutSideParams::Stretch(), LayoutSideParams::Content())
}
));
+ */
+
+ window.AddChild(
+ Border::Create({
+ MarginContainer::Create(Thickness(50, 50), {
+ LinearLayout::Create(LinearLayout::Orientation::Vertical, {
+ Button::Create({
+ TextBlock::Create(L"Button")
+ }),
+ TextBox::Create()
+ })
+ })
+ })
+ );
window.Show();
diff --git a/src/ui/control.cpp b/src/ui/control.cpp
index 81cfe9b1..86101f9a 100644
--- a/src/ui/control.cpp
+++ b/src/ui/control.cpp
@@ -257,7 +257,7 @@ namespace cru {
{
SetPositionRelative(rect.GetLeftTop());
SetSize(rect.GetSize());
- OnLayout(rect);
+ OnLayout(Rect(Point::Zero(), rect.GetSize()));
}
Size Control::GetDesiredSize() const
@@ -617,24 +617,137 @@ namespace cru {
const auto layout_params = control->GetLayoutParams();
const auto size = control->GetDesiredSize();
- auto&& calculate_anchor = [](const Alignment alignment, const float layout_length, const float control_length) -> float
+ auto&& calculate_anchor = [](const float anchor, const Alignment alignment, const float layout_length, const float control_length) -> float
{
switch (alignment)
{
case Alignment::Center:
- return (layout_length - control_length) / 2;
+ return anchor + (layout_length - control_length) / 2;
case Alignment::Start:
- return 0;
+ return anchor;
case Alignment::End:
- return layout_length - control_length;
+ return anchor + layout_length - control_length;
default:
UnreachableCode();
}
};
control->Layout(Rect(Point(
- calculate_anchor(layout_params->width.alignment, rect.width, size.width),
- calculate_anchor(layout_params->height.alignment, rect.height, size.height)
+ calculate_anchor(rect.left, layout_params->width.alignment, rect.width, size.width),
+ calculate_anchor(rect.top, layout_params->height.alignment, rect.height, size.height)
+ ), size));
+ });
+ }
+
+ inline Size ThicknessToSize(const Thickness& thickness)
+ {
+ return Size(thickness.left + thickness.right, thickness.top + thickness.bottom);
+ }
+
+ 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 Control::DefaultMeasureWithPadding(const Size& available_size, const Thickness& padding)
+ {
+ const auto layout_params = GetLayoutParams();
+ const auto padding_size = ThicknessToSize(padding);
+
+ if (!layout_params->Validate())
+ throw std::runtime_error("LayoutParams is not valid. Please check it.");
+
+ auto&& get_available_length_for_child = [](const LayoutSideParams& layout_length, const float available_length) -> float
+ {
+ switch (layout_length.mode)
+ {
+ case MeasureMode::Exactly:
+ {
+ return std::min(layout_length.length, available_length);
+ }
+ case MeasureMode::Stretch:
+ case MeasureMode::Content:
+ {
+ return available_length;
+ }
+ default:
+ UnreachableCode();
+ }
+ };
+
+ Size size_for_children(get_available_length_for_child(layout_params->width, available_size.width),
+ get_available_length_for_child(layout_params->height, available_size.height));
+
+ size_for_children = AtLeast0(size_for_children - padding_size);
+
+ auto max_child_size = Size::Zero();
+ ForeachChild([&](Control* control)
+ {
+ control->Measure(size_for_children);
+ const auto&& size = control->GetDesiredSize();
+ if (max_child_size.width < size.width)
+ max_child_size.width = size.width;
+ if (max_child_size.height < size.height)
+ max_child_size.height = size.height;
+ });
+
+ auto&& calculate_final_length = [](const LayoutSideParams& layout_length, const float length_for_children, const float max_child_length) -> float
+ {
+ switch (layout_length.mode)
+ {
+ case MeasureMode::Exactly:
+ case MeasureMode::Stretch:
+ return length_for_children;
+ case MeasureMode::Content:
+ return max_child_length;
+ default:
+ UnreachableCode();
+ }
+ };
+
+ return Size(
+ calculate_final_length(layout_params->width, size_for_children.width, max_child_size.width),
+ calculate_final_length(layout_params->height, size_for_children.height, max_child_size.height)
+ ) + padding_size;
+ }
+
+ void Control::DefaultLayoutWithPadding(const Rect& rect, const Thickness& padding)
+ {
+ const Rect final_rect(
+ rect.left + padding.left,
+ rect.top + padding.top,
+ rect.width - padding.left - padding.right,
+ rect.height - padding.top - padding.bottom
+ );
+
+ ForeachChild([final_rect](Control* control)
+ {
+ const auto layout_params = control->GetLayoutParams();
+ const auto size = control->GetDesiredSize();
+
+ auto&& calculate_anchor = [](const float anchor, const Alignment alignment, const float layout_length, const float control_length) -> float
+ {
+ switch (alignment)
+ {
+ case Alignment::Center:
+ return anchor + (layout_length - control_length) / 2;
+ case Alignment::Start:
+ return anchor;
+ case Alignment::End:
+ return anchor + layout_length - control_length;
+ default:
+ UnreachableCode();
+ }
+ };
+
+ control->Layout(Rect(Point(
+ calculate_anchor(final_rect.left, layout_params->width.alignment, final_rect.width, size.width),
+ calculate_anchor(final_rect.top, layout_params->height.alignment, final_rect.height, size.height)
), size));
});
}
diff --git a/src/ui/control.h b/src/ui/control.h
index 98d41d0d..964b61c4 100644
--- a/src/ui/control.h
+++ b/src/ui/control.h
@@ -309,6 +309,9 @@ namespace cru
virtual Size OnMeasure(const Size& available_size);
virtual void OnLayout(const Rect& rect);
+ Size DefaultMeasureWithPadding(const Size& available_size, const Thickness& padding);
+ void DefaultLayoutWithPadding(const Rect& rect, const Thickness& padding);
+
private:
// Only for layout manager to use.
// Check if the old position is updated to current position.
diff --git a/src/ui/controls/border.cpp b/src/ui/controls/border.cpp
index f79d610f..1caed91d 100644
--- a/src/ui/controls/border.cpp
+++ b/src/ui/controls/border.cpp
@@ -6,53 +6,32 @@ namespace cru::ui::controls
{
using graph::CreateSolidBrush;
- Border::Border() : Control(true)
+ Border::Border() : Control(true), border_delegate_(this)
{
- border_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::Black));
- }
-
- void Border::SetDrawBorder(bool draw_border)
- {
- draw_border_ = draw_border;
- Repaint();
- }
- void Border::SetBorderBrush(Microsoft::WRL::ComPtr<ID2D1Brush> border_brush)
- {
- border_brush_ = std::move(border_brush);
- Repaint();
- }
-
- void Border::SetBorderWidth(const float border_width)
- {
- border_width_ = border_width;
- Repaint();
}
- void Border::SetBorderStrokeStyle(Microsoft::WRL::ComPtr<ID2D1StrokeStyle> stroke_style)
+ void Border::SetDrawBorder(const bool draw_border)
{
- border_stroke_style_ = std::move(stroke_style);
+ draw_border_ = draw_border;
Repaint();
}
- void Border::SetBorderRadiusX(const float border_radius_x)
+ void Border::OnDraw(ID2D1DeviceContext* device_context)
{
- border_radius_x_ = border_radius_x;
- Repaint();
+ if (draw_border_)
+ {
+ border_delegate_.Draw(device_context, GetSize());
+ }
}
- void Border::SetBorderRadiusY(const float border_radius_y)
+ Size Border::OnMeasure(const Size& available_size)
{
- border_radius_y_ = border_radius_y;
- Repaint();
+ return Control::DefaultMeasureWithPadding(available_size, border_delegate_.GetBorderThickness());
}
- void Border::OnDraw(ID2D1DeviceContext* device_context)
+ void Border::OnLayout(const Rect& rect)
{
- if (draw_border_)
- {
- const auto size = GetSize();
- device_context->DrawRoundedRectangle(D2D1::RoundedRect(D2D1::RectF(0.0f, 0.0f, size.width, size.height), border_radius_x_, border_radius_y_), border_brush_.Get(), border_width_, border_stroke_style_.Get());
- }
+ Control::DefaultLayoutWithPadding(rect, border_delegate_.GetBorderThickness());
}
}
diff --git a/src/ui/controls/border.h b/src/ui/controls/border.h
index 74e12c92..7880e690 100644
--- a/src/ui/controls/border.h
+++ b/src/ui/controls/border.h
@@ -3,6 +3,7 @@
#include <initializer_list>
#include "ui/control.h"
+#include "border_delegate.h"
namespace cru::ui::controls
{
@@ -34,52 +35,20 @@ namespace cru::ui::controls
void SetDrawBorder(bool draw_border);
- Microsoft::WRL::ComPtr<ID2D1Brush> GetBorderBrush() const
+ BorderProperty::Ptr GetBorderProperty() const
{
- return border_brush_;
+ return border_delegate_.GetBorderProperty();
}
- void SetBorderBrush(Microsoft::WRL::ComPtr<ID2D1Brush> border_brush);
-
- float GetBorderWidth() const
- {
- return border_width_;
- }
-
- void SetBorderWidth(float border_width);
-
- Microsoft::WRL::ComPtr<ID2D1StrokeStyle> GetBorderStrokeStyle() const
- {
- return border_stroke_style_;
- }
-
- void SetBorderStrokeStyle(Microsoft::WRL::ComPtr<ID2D1StrokeStyle> stroke_style);
-
- float GetBorderRadiusX() const
- {
- return border_radius_x_;
- }
-
- void SetBorderRadiusX(float border_radius_x);
-
- float GetBorderRadiusY() const
- {
- return border_radius_y_;
- }
-
- void SetBorderRadiusY(float border_radius_y);
-
protected:
void OnDraw(ID2D1DeviceContext* device_context) override;
+ Size OnMeasure(const Size& available_size) override;
+ void OnLayout(const Rect& rect) override;
+
private:
bool draw_border_ = true;
- Microsoft::WRL::ComPtr<ID2D1Brush> border_brush_;
- float border_width_ = 1.0f;
- Microsoft::WRL::ComPtr<ID2D1StrokeStyle> border_stroke_style_ = nullptr;
-
- float border_radius_x_ = 0.0f;
- float border_radius_y_ = 0.0f;
+ BorderDelegate border_delegate_;
};
}
diff --git a/src/ui/controls/border_delegate.cpp b/src/ui/controls/border_delegate.cpp
new file mode 100644
index 00000000..c8855e0f
--- /dev/null
+++ b/src/ui/controls/border_delegate.cpp
@@ -0,0 +1,97 @@
+#include "border_delegate.h"
+#include "graph/graph.h"
+
+namespace cru::ui::controls
+{
+ BorderProperty::Ptr BorderProperty::Create()
+ {
+ return std::make_shared<BorderProperty>(graph::CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::Black)));
+ }
+
+ BorderProperty::BorderProperty(Microsoft::WRL::ComPtr<ID2D1Brush> brush)
+ : brush_(std::move(brush))
+ {
+
+ }
+
+ void BorderProperty::SetBrush(Microsoft::WRL::ComPtr<ID2D1Brush> brush)
+ {
+ brush_ = std::move(brush);
+ RaisePropertyChangedEvent(brush_property_name);
+ }
+
+ void BorderProperty::SetWidth(const float width)
+ {
+ width_ = width;
+ RaisePropertyChangedEvent(width_property_name);
+ }
+
+ void BorderProperty::SetStrokeStyle(Microsoft::WRL::ComPtr<ID2D1StrokeStyle> stroke_style)
+ {
+ stroke_style_ = std::move(stroke_style);
+ RaisePropertyChangedEvent(stroke_style_property_name);
+ }
+
+ void BorderProperty::SetRadiusX(const float radius_x)
+ {
+ radius_x_ = radius_x;
+ RaisePropertyChangedEvent(radius_x_property_name);
+ }
+
+ void BorderProperty::SetRadiusY(const float radius_y)
+ {
+ radius_y_ = radius_y;
+ RaisePropertyChangedEvent(radius_y_property_name);
+ }
+
+ BorderDelegate::BorderDelegate(Control* control)
+ : BorderDelegate(control, BorderProperty::Create())
+ {
+
+ }
+
+ BorderDelegate::BorderDelegate(Control* control, std::shared_ptr<BorderProperty> border_property)\
+ : control_(control),
+ border_property_changed_listener_(CreateFunctionPtr<void(String)>([this](String property_name)
+ {
+ if (property_name == BorderProperty::width_property_name)
+ control_->InvalidateLayout();
+ control_->Repaint();
+ }))
+ {
+ border_property_ = std::move(border_property);
+ border_property_->AddPropertyChangedListener(border_property_changed_listener_);
+ }
+
+ BorderDelegate::~BorderDelegate()
+ {
+ border_property_->RemovePropertyChangedListener(border_property_changed_listener_);
+ }
+
+ void BorderDelegate::SetBorderProperty(std::shared_ptr<BorderProperty> border_property)
+ {
+ border_property_->RemovePropertyChangedListener(border_property_changed_listener_);
+ border_property_ = std::move(border_property);
+ border_property_->AddPropertyChangedListener(border_property_changed_listener_);
+ control_->Repaint();
+ }
+
+ void BorderDelegate::Draw(ID2D1DeviceContext* device_context, const Size& size) const
+ {
+ device_context->DrawRoundedRectangle(
+ D2D1::RoundedRect(
+ D2D1::RectF(
+ border_property_->GetWidth() / 2.0f,
+ border_property_->GetWidth() / 2.0f,
+ size.width - border_property_->GetWidth(),
+ size.height - border_property_->GetWidth()
+ ),
+ border_property_->GetRadiusX(),
+ border_property_->GetRadiusY()
+ ),
+ border_property_->GetBrush().Get(),
+ border_property_->GetWidth(),
+ border_property_->GetStrokeStyle().Get()
+ );
+ }
+}
diff --git a/src/ui/controls/border_delegate.h b/src/ui/controls/border_delegate.h
new file mode 100644
index 00000000..6d8663e9
--- /dev/null
+++ b/src/ui/controls/border_delegate.h
@@ -0,0 +1,101 @@
+#pragma once
+
+#include "ui/control.h"
+
+namespace cru::ui::controls
+{
+ class BorderProperty : public PropertyChangedNotifyObject
+ {
+ public:
+ using Ptr = std::shared_ptr<BorderProperty>;
+ static Ptr Create();
+
+ constexpr static auto brush_property_name = L"Brush";
+ constexpr static auto width_property_name = L"Width";
+ constexpr static auto stroke_style_property_name = L"StrokeStyle";
+ constexpr static auto radius_x_property_name = L"RadiusX";
+ constexpr static auto radius_y_property_name = L"RadiusY";
+
+ BorderProperty() = default;
+ explicit BorderProperty(Microsoft::WRL::ComPtr<ID2D1Brush> brush);
+ BorderProperty(const BorderProperty& other) = delete;
+ BorderProperty(BorderProperty&& other) = delete;
+ BorderProperty& operator=(const BorderProperty& other) = delete;
+ BorderProperty& operator=(BorderProperty&& other) = delete;
+ ~BorderProperty() override = default;
+
+
+ Microsoft::WRL::ComPtr<ID2D1Brush> GetBrush() const
+ {
+ return brush_;
+ }
+
+ float GetWidth() const
+ {
+ return 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);
+ void SetWidth(float width);
+ void SetStrokeStyle(Microsoft::WRL::ComPtr<ID2D1StrokeStyle> stroke_style);
+ void SetRadiusX(float radius_x);
+ void SetRadiusY(float radius_y);
+
+ private:
+ Microsoft::WRL::ComPtr<ID2D1Brush> brush_ = nullptr;
+ float width_ = 1.0f;
+ Microsoft::WRL::ComPtr<ID2D1StrokeStyle> stroke_style_ = nullptr;
+ float radius_x_ = 0.0f;
+ float radius_y_ = 0.0f;
+ };
+
+
+ // BorderDelegate is a delegate for border painting and layout.
+ // It must bind a control and not change the binding.
+ // But multiple BorderDelegate may share a common BorderProperty.
+ class BorderDelegate : public Object
+ {
+ public:
+ explicit BorderDelegate(Control* control);
+ BorderDelegate(Control* control, std::shared_ptr<BorderProperty> border_property);
+ BorderDelegate(const BorderDelegate& other) = delete;
+ BorderDelegate(BorderDelegate&& other) = delete;
+ BorderDelegate& operator=(const BorderDelegate& other) = delete;
+ BorderDelegate& operator=(BorderDelegate&& other) = delete;
+ ~BorderDelegate() override;
+
+ std::shared_ptr<BorderProperty> GetBorderProperty() const
+ {
+ return border_property_;
+ }
+
+ void SetBorderProperty(std::shared_ptr<BorderProperty> border_property);
+
+ void Draw(ID2D1DeviceContext* device_context, const Size& size) const;
+
+ Thickness GetBorderThickness() const
+ {
+ return Thickness(border_property_->GetWidth());
+ }
+
+ private:
+ Control* control_;
+ std::shared_ptr<BorderProperty> border_property_;
+ FunctionPtr<void(String)> border_property_changed_listener_;
+ };
+}
diff --git a/src/ui/controls/button.cpp b/src/ui/controls/button.cpp
index b7614f93..2ecc9f14 100644
--- a/src/ui/controls/button.cpp
+++ b/src/ui/controls/button.cpp
@@ -8,26 +8,46 @@ namespace cru::ui::controls
Button::Button() : Control(true)
{
- normal_border_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::RoyalBlue));
- pressed_border_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::MediumBlue));
- current_border_brush_ = normal_border_brush_.Get();
+ normal_border_border_ = BorderProperty::Create();
+ normal_border_border_->SetBrush(CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::RoyalBlue)));
+ normal_border_border_->SetWidth(2);
+ normal_border_border_->SetRadiusX(6);
+ normal_border_border_->SetRadiusY(6);
+
+ pressed_border_border_ = BorderProperty::Create();
+ pressed_border_border_->SetBrush(CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::Blue)));
+ pressed_border_border_->SetWidth(2);
+ pressed_border_border_->SetRadiusX(6);
+ pressed_border_border_->SetRadiusY(6);
+
+ border_delegate_ = std::make_unique<BorderDelegate>(this, normal_border_border_);
}
void Button::OnDraw(ID2D1DeviceContext* device_context)
{
Control::OnDraw(device_context);
- device_context->DrawRoundedRectangle(D2D1::RoundedRect(D2D1::RectF(0, 0, GetSize().width, GetSize().height), 6, 6), current_border_brush_, 2);
+ border_delegate_->Draw(device_context, GetSize());
+ }
+
+ Size Button::OnMeasure(const Size& available_size)
+ {
+ return Control::DefaultMeasureWithPadding(available_size, border_delegate_->GetBorderThickness());
+ }
+
+ void Button::OnLayout(const Rect& rect)
+ {
+ Control::DefaultLayoutWithPadding(rect, border_delegate_->GetBorderThickness());
}
void Button::OnMouseClickBegin(MouseButton button)
{
- current_border_brush_ = pressed_border_brush_.Get();
+ border_delegate_->SetBorderProperty(pressed_border_border_);
Repaint();
}
void Button::OnMouseClickEnd(MouseButton button)
{
- current_border_brush_ = normal_border_brush_.Get();
+ border_delegate_->SetBorderProperty(normal_border_border_);
Repaint();
}
}
diff --git a/src/ui/controls/button.h b/src/ui/controls/button.h
index bd3f6eb3..4b57a5a3 100644
--- a/src/ui/controls/button.h
+++ b/src/ui/controls/button.h
@@ -3,6 +3,7 @@
#include <initializer_list>
#include "ui/control.h"
+#include "border_delegate.h"
namespace cru::ui::controls
{
@@ -30,12 +31,16 @@ namespace cru::ui::controls
protected:
void OnDraw(ID2D1DeviceContext* device_context) override;
+ Size OnMeasure(const Size& available_size) override;
+ void OnLayout(const Rect& rect) override;
+
void OnMouseClickBegin(MouseButton button) override final;
void OnMouseClickEnd(MouseButton button) override final;
private:
- Microsoft::WRL::ComPtr<ID2D1Brush> normal_border_brush_;
- Microsoft::WRL::ComPtr<ID2D1Brush> pressed_border_brush_;
- ID2D1Brush* current_border_brush_;
+ std::unique_ptr<BorderDelegate> border_delegate_;
+
+ BorderProperty::Ptr normal_border_border_;
+ BorderProperty::Ptr pressed_border_border_;
};
}
diff --git a/src/ui/controls/margin_container.cpp b/src/ui/controls/margin_container.cpp
index 1f331d32..12dde025 100644
--- a/src/ui/controls/margin_container.cpp
+++ b/src/ui/controls/margin_container.cpp
@@ -25,39 +25,11 @@ namespace cru::ui::controls
Size MarginContainer::OnMeasure(const Size& available_size)
{
- const auto margin_size = Size(margin_.left + margin_.right, margin_.top + margin_.bottom);
- const auto coerced_available_size = AtLeast0(available_size - margin_size);
- return Control::OnMeasure(coerced_available_size) + margin_size;
+ return DefaultMeasureWithPadding(available_size, margin_);
}
void MarginContainer::OnLayout(const Rect& rect)
{
- const auto anchor = Point(margin_.left, margin_.top);
- const auto margin_size = Size(margin_.left + margin_.right, margin_.top + margin_.bottom);
- ForeachChild([anchor, margin_size, rect](Control* control)
- {
- const auto layout_params = control->GetLayoutParams();
- const auto size = control->GetDesiredSize();
-
- auto&& calculate_anchor = [](const float anchor, const Alignment alignment, const float layout_length, const float control_length) -> float
- {
- switch (alignment)
- {
- case Alignment::Center:
- return anchor + (layout_length - control_length) / 2;
- case Alignment::Start:
- return anchor;
- case Alignment::End:
- return anchor + layout_length - control_length;
- default:
- UnreachableCode();
- }
- };
-
- control->Layout(Rect(Point(
- calculate_anchor(anchor.x, layout_params->width.alignment, rect.width - margin_size.width, size.width),
- calculate_anchor(anchor.y, layout_params->height.alignment, rect.height - margin_size.height, size.height)
- ), size));
- });
+ DefaultLayoutWithPadding(rect, margin_);
}
}
diff --git a/src/ui/controls/text_box.cpp b/src/ui/controls/text_box.cpp
index 4a4114ab..0d65f1ad 100644
--- a/src/ui/controls/text_box.cpp
+++ b/src/ui/controls/text_box.cpp
@@ -26,21 +26,28 @@ namespace cru::ui::controls
is_caret_show_ = !is_caret_show_;
Repaint();
});
+
+ border_delegate_ = std::make_unique<BorderDelegate>(this);
}
TextBox::~TextBox() = default;
void TextBox::OnDraw(ID2D1DeviceContext* device_context)
{
- TextControl::OnDraw(device_context);
- if (is_caret_show_)
+ border_delegate_->Draw(device_context, GetSize());
+ const auto border_thickness = border_delegate_->GetBorderThickness();
+ graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(border_thickness.left, border_thickness.top), [this](ID2D1DeviceContext* device_context)
{
- const auto caret_half_width = Application::GetInstance()->GetCaretInfo().half_caret_width;
- FLOAT x, y;
- DWRITE_HIT_TEST_METRICS metrics{};
- ThrowIfFailed(text_layout_->HitTestTextPosition(caret_position_, FALSE, &x, &y, &metrics));
- device_context->FillRectangle(D2D1::RectF(metrics.left - caret_half_width, metrics.top, metrics.left + caret_half_width, metrics.top + metrics.height), caret_brush_.Get());
- }
+ TextControl::OnDraw(device_context);
+ if (is_caret_show_)
+ {
+ const auto caret_half_width = Application::GetInstance()->GetCaretInfo().half_caret_width;
+ FLOAT x, y;
+ DWRITE_HIT_TEST_METRICS metrics{};
+ ThrowIfFailed(text_layout_->HitTestTextPosition(caret_position_, FALSE, &x, &y, &metrics));
+ device_context->FillRectangle(D2D1::RectF(metrics.left - caret_half_width, metrics.top, metrics.left + caret_half_width, metrics.top + metrics.height), caret_brush_.Get());
+ }
+ });
}
void TextBox::OnGetFocusCore(events::FocusChangeEventArgs& args)
@@ -131,6 +138,11 @@ namespace cru::ui::controls
}
}
+ Size TextBox::OnMeasure(const Size& available_size)
+ {
+ return TextMeasureWithPadding(available_size, border_delegate_->GetBorderThickness());
+ }
+
void TextBox::RequestChangeCaretPosition(const unsigned position)
{
caret_position_ = position;
diff --git a/src/ui/controls/text_box.h b/src/ui/controls/text_box.h
index 07c4abe4..a6e4566d 100644
--- a/src/ui/controls/text_box.h
+++ b/src/ui/controls/text_box.h
@@ -2,6 +2,7 @@
#include "text_control.h"
#include "timer.h"
+#include "border_delegate.h"
namespace cru::ui::controls
{
@@ -36,6 +37,8 @@ namespace cru::ui::controls
void OnKeyDownCore(events::KeyEventArgs& args) override final;
void OnCharCore(events::CharEventArgs& args) override final;
+ Size OnMeasure(const Size& available_size) override;
+
void RequestChangeCaretPosition(unsigned position) override;
private:
@@ -44,5 +47,7 @@ namespace cru::ui::controls
ActionPtr caret_action_;
Microsoft::WRL::ComPtr<ID2D1Brush> caret_brush_;
bool is_caret_show_ = false;
+
+ std::unique_ptr<BorderDelegate> border_delegate_;
};
}
diff --git a/src/ui/controls/text_control.cpp b/src/ui/controls/text_control.cpp
index 692c4451..0b730500 100644
--- a/src/ui/controls/text_control.cpp
+++ b/src/ui/controls/text_control.cpp
@@ -259,6 +259,76 @@ namespace cru::ui::controls
return result_size;
}
+ inline Size ThicknessToSize(const Thickness& thickness)
+ {
+ return Size(thickness.left + thickness.right, thickness.top + thickness.bottom);
+ }
+
+ 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 TextControl::TextMeasureWithPadding(const Size& available_size, const Thickness& padding)
+ {
+ const auto layout_params = GetLayoutParams();
+ const auto padding_size = ThicknessToSize(padding);
+
+ auto&& get_measure_length = [](const LayoutSideParams& layout_length, const float available_length) -> float
+ {
+ switch (layout_length.mode)
+ {
+ case MeasureMode::Exactly:
+ {
+ return std::min(layout_length.length, available_length);
+ }
+ case MeasureMode::Stretch:
+ case MeasureMode::Content:
+ {
+ return available_length;
+ }
+ default:
+ UnreachableCode();
+ }
+ };
+
+ Size measure_size(get_measure_length(layout_params->width, available_size.width),
+ get_measure_length(layout_params->height, available_size.height));
+
+ measure_size = AtLeast0(measure_size - padding_size);
+
+ ThrowIfFailed(text_layout_->SetMaxWidth(measure_size.width));
+ ThrowIfFailed(text_layout_->SetMaxHeight(measure_size.height));
+
+ DWRITE_TEXT_METRICS metrics{};
+
+ ThrowIfFailed(text_layout_->GetMetrics(&metrics));
+
+ const Size measure_result(metrics.width, metrics.height);
+
+ auto&& calculate_final_length = [](const LayoutSideParams& layout_length, const float measure_length, const float measure_result_length) -> float
+ {
+ if ((layout_length.mode == MeasureMode::Stretch ||
+ layout_length.mode == MeasureMode::Exactly)
+ && measure_result_length < measure_length)
+ return measure_length;
+ else
+ return measure_result_length;
+ };
+
+ const Size result_size(
+ calculate_final_length(layout_params->width, measure_size.width, measure_result.width),
+ calculate_final_length(layout_params->height, measure_size.height, measure_result.height)
+ );
+
+ return result_size + padding_size;
+ }
+
void TextControl::RequestChangeCaretPosition(unsigned position)
{
diff --git a/src/ui/controls/text_control.h b/src/ui/controls/text_control.h
index a24766dc..2ead7c54 100644
--- a/src/ui/controls/text_control.h
+++ b/src/ui/controls/text_control.h
@@ -76,7 +76,9 @@ namespace cru::ui::controls
void OnLoseFocusCore(events::FocusChangeEventArgs& args) override;
- Size OnMeasure(const Size& available_size) override final;
+ Size OnMeasure(const Size& available_size) override;
+
+ Size TextMeasureWithPadding(const Size& available_size, const Thickness& padding);
virtual void RequestChangeCaretPosition(unsigned position);