diff options
author | Yuqian Yang <crupest@outlook.com> | 2018-10-01 17:11:11 +0000 |
---|---|---|
committer | Yuqian Yang <crupest@outlook.com> | 2018-10-01 17:11:11 +0000 |
commit | 30ecda8bb354d5982978af97aa90b5f49d9ea195 (patch) | |
tree | a271bddb244fa2041f14f8d46d249457cee09e5f /src/ui/controls | |
parent | 398b8f3ba535bb43c4b8593e3027c14894a7a211 (diff) | |
parent | 040a6c18f18100b825a56443a73aa1de64e4518c (diff) | |
download | cru-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
Diffstat (limited to 'src/ui/controls')
-rw-r--r-- | src/ui/controls/border.cpp | 45 | ||||
-rw-r--r-- | src/ui/controls/border.h | 45 | ||||
-rw-r--r-- | src/ui/controls/border_delegate.cpp | 97 | ||||
-rw-r--r-- | src/ui/controls/border_delegate.h | 101 | ||||
-rw-r--r-- | src/ui/controls/button.cpp | 32 | ||||
-rw-r--r-- | src/ui/controls/button.h | 11 | ||||
-rw-r--r-- | src/ui/controls/margin_container.cpp | 32 | ||||
-rw-r--r-- | src/ui/controls/text_box.cpp | 28 | ||||
-rw-r--r-- | src/ui/controls/text_box.h | 5 | ||||
-rw-r--r-- | src/ui/controls/text_control.cpp | 70 | ||||
-rw-r--r-- | src/ui/controls/text_control.h | 4 |
11 files changed, 351 insertions, 119 deletions
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); |