diff options
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/control.cpp | 681 | ||||
-rw-r--r-- | src/ui/control.hpp | 301 | ||||
-rw-r--r-- | src/ui/controls/button.hpp | 2 | ||||
-rw-r--r-- | src/ui/controls/list_item.hpp | 2 | ||||
-rw-r--r-- | src/ui/controls/scroll_control.hpp | 2 | ||||
-rw-r--r-- | src/ui/render/linear_layout_render_object.cpp | 0 | ||||
-rw-r--r-- | src/ui/render/linear_layout_render_object.hpp | 1 | ||||
-rw-r--r-- | src/ui/render/render_object.cpp | 176 | ||||
-rw-r--r-- | src/ui/render/render_object.hpp | 302 | ||||
-rw-r--r-- | src/ui/ui_base.hpp | 39 | ||||
-rw-r--r-- | src/ui/window.cpp | 23 | ||||
-rw-r--r-- | src/ui/window.hpp | 28 | ||||
-rw-r--r-- | src/ui/window_class.cpp | 25 | ||||
-rw-r--r-- | src/ui/window_class.hpp | 26 |
14 files changed, 310 insertions, 1298 deletions
diff --git a/src/ui/control.cpp b/src/ui/control.cpp index 9388c719..617c50c7 100644 --- a/src/ui/control.cpp +++ b/src/ui/control.cpp @@ -17,53 +17,6 @@ namespace cru::ui { - Control::Control() - { - mouse_leave_event.bubble.AddHandler([this](events::MouseEventArgs& args) - { - if (args.GetOriginalSender() != this) - return; - for (auto& is_mouse_click_valid : is_mouse_click_valid_map_) - { - if (is_mouse_click_valid.second) - { - is_mouse_click_valid.second = false; - OnMouseClickEnd(is_mouse_click_valid.first); - } - } - }); - - mouse_down_event.bubble.AddHandler([this](events::MouseButtonEventArgs& args) - { - if (args.GetOriginalSender() != this) - return; - - if (is_focus_on_pressed_ && args.GetSender() == args.GetOriginalSender()) - RequestFocus(); - const auto button = args.GetMouseButton(); - is_mouse_click_valid_map_[button] = true; - OnMouseClickBegin(button); - }); - - mouse_up_event.bubble.AddHandler([this](events::MouseButtonEventArgs& args) - { - if (args.GetOriginalSender() != this) - return; - - const auto button = args.GetMouseButton(); - if (is_mouse_click_valid_map_[button]) - { - is_mouse_click_valid_map_[button] = false; - OnMouseClickEnd(button); - const auto point = args.GetPoint(GetWindow()); - InvokeLater([this, button, point] - { - DispatchEvent(this, &Control::mouse_click_event, nullptr, point, button); - }); - } - }); - } - void Control::SetParent(Control* parent) { @@ -74,19 +27,6 @@ namespace cru::ui OnParentChanged(old_parent, new_parent); } - void Control::SetInternalParent(Control* internal_parent) - { - const auto old_internal_parent = GetInternalParent(); - const auto old_parent = GetParent(); - internal_parent_ = internal_parent; - const auto new_internal_parent = GetInternalParent(); - const auto new_parent = GetParent(); - if (old_parent != new_parent) - OnParentChanged(old_parent, new_parent); - if (old_internal_parent != new_internal_parent) - OnInternalParentChanged(old_internal_parent, new_internal_parent); - } - void Control::SetDescendantWindow(Window* window) { if (window == nullptr && window_ == nullptr) @@ -112,199 +52,29 @@ namespace cru::ui }); } - - void TraverseDescendantsInternal(Control* control, const std::function<void(Control*)>& predicate) - { - predicate(control); - for (auto c: control->GetInternalChildren()) - TraverseDescendantsInternal(c, predicate); - } - void Control::TraverseDescendants(const std::function<void(Control*)>& predicate) { TraverseDescendantsInternal(this, predicate); } - Point Control::GetOffset() - { - return rect_.GetLeftTop(); - } - - Size Control::GetSize() - { - return rect_.GetSize(); - } - - void Control::SetRect(const Rect& rect) - { - const auto old_rect = rect_; - rect_ = rect; - - RegenerateGeometryInfo(); - - OnRectChange(old_rect, rect); - - if (auto window = GetWindow()) - window->InvalidateDraw(); - } - - namespace + void Control::TraverseDescendantsInternal(Control * control, const std::function<void(Control*)>& predicate) { -#ifdef CRU_DEBUG_LAYOUT - Microsoft::WRL::ComPtr<ID2D1Geometry> CalculateSquareRingGeometry(const Rect& out, const Rect& in) - { - const auto d2d1_factory = graph::GraphManager::GetInstance()->GetD2D1Factory(); - Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> out_geometry; - ThrowIfFailed(d2d1_factory->CreateRectangleGeometry(Convert(out), &out_geometry)); - Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> in_geometry; - ThrowIfFailed(d2d1_factory->CreateRectangleGeometry(Convert(in), &in_geometry)); - Microsoft::WRL::ComPtr<ID2D1PathGeometry> result_geometry; - ThrowIfFailed(d2d1_factory->CreatePathGeometry(&result_geometry)); - Microsoft::WRL::ComPtr<ID2D1GeometrySink> sink; - ThrowIfFailed(result_geometry->Open(&sink)); - ThrowIfFailed(out_geometry->CombineWithGeometry(in_geometry.Get(), D2D1_COMBINE_MODE_EXCLUDE, D2D1::Matrix3x2F::Identity(), sink.Get())); - ThrowIfFailed(sink->Close()); - return result_geometry; - } -#endif + predicate(control); + for (auto c: control->GetInternalChildren()) + TraverseDescendantsInternal(c, predicate); } - Point Control::GetPositionAbsolute() const - { - return position_cache_.lefttop_position_absolute; - } Point Control::ControlToWindow(const Point& point) const { - return Point(point.x + position_cache_.lefttop_position_absolute.x, - point.y + position_cache_.lefttop_position_absolute.y); + const auto position = GetPositionInWindow(); + return Point(point.x + position.x, point.y + position.y); } Point Control::WindowToControl(const Point & point) const { - return Point(point.x - position_cache_.lefttop_position_absolute.x, - point.y - position_cache_.lefttop_position_absolute.y); - } - - void Control::RefreshDescendantPositionCache() - { - auto point = Point::Zero(); - auto parent = this; - while ((parent = parent->GetParent())) - { - const auto p = parent->GetOffset(); - point.x += p.x; - point.y += p.y; - } - RefreshControlPositionCacheInternal(this, point); - } - - void Control::RefreshControlPositionCacheInternal(Control* control, const Point& parent_lefttop_absolute) - { - const auto position = control->GetOffset(); - const Point lefttop( - parent_lefttop_absolute.x + position.x, - parent_lefttop_absolute.y + position.y - ); - control->position_cache_.lefttop_position_absolute = lefttop; - for(auto c : control->GetInternalChildren()) - { - RefreshControlPositionCacheInternal(c, lefttop); - } - } - - bool Control::IsPointInside(const Point & point) - { - const auto border_geometry = geometry_info_.border_geometry; - if (border_geometry != nullptr) - { - if (IsBordered()) - { - BOOL contains; - border_geometry->FillContainsPoint(Convert(point), D2D1::Matrix3x2F::Identity(), &contains); - if (!contains) - border_geometry->StrokeContainsPoint(Convert(point), GetBorderProperty().GetStrokeWidth(), nullptr, D2D1::Matrix3x2F::Identity(), &contains); - return contains != 0; - } - else - { - BOOL contains; - border_geometry->FillContainsPoint(Convert(point), D2D1::Matrix3x2F::Identity(), &contains); - return contains != 0; - } - } - return false; - } - - Control* Control::HitTest(const Point& point) - { - const auto point_inside = IsPointInside(point); - - if (IsClipContent()) - { - if (!point_inside) - return nullptr; - if (geometry_info_.content_geometry != nullptr) - { - BOOL contains; - ThrowIfFailed(geometry_info_.content_geometry->FillContainsPoint(Convert(point), D2D1::Matrix3x2F::Identity(), &contains)); - if (contains == 0) - return this; - } - } - - const auto& children = GetInternalChildren(); - - for (auto i = children.crbegin(); i != children.crend(); ++i) - { - const auto&& lefttop = (*i)->GetOffset(); - const auto&& coerced_point = Point(point.x - lefttop.x, point.y - lefttop.y); - const auto child_hit_test_result = (*i)->HitTest(coerced_point); - if (child_hit_test_result != nullptr) - return child_hit_test_result; - } - - return point_inside ? this : nullptr; - } - - void Control::SetClipContent(const bool clip) - { - if (clip_content_ == clip) - return; - - clip_content_ = clip; - InvalidateDraw(); - } - - void Control::Draw(ID2D1DeviceContext* device_context) - { - D2D1::Matrix3x2F old_transform; - device_context->GetTransform(&old_transform); - - const auto position = GetOffset(); - device_context->SetTransform(old_transform * D2D1::Matrix3x2F::Translation(position.x, position.y)); - - OnDrawDecoration(device_context); - - const auto set_layer = geometry_info_.content_geometry != nullptr && IsClipContent(); - if (set_layer) - device_context->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), geometry_info_.content_geometry.Get()), nullptr); - - OnDrawCore(device_context); - - for (auto child : GetInternalChildren()) - child->Draw(device_context); - - if (set_layer) - device_context->PopLayer(); - - device_context->SetTransform(old_transform); - } - - void Control::InvalidateDraw() - { - if (window_ != nullptr) - window_->InvalidateDraw(); + const auto position = GetPositionInWindow(); + return Point(point.x - position.x, point.y - position.y); } bool Control::RequestFocus() @@ -325,109 +95,6 @@ namespace cru::ui return window->GetFocusControl() == this; } - void Control::InvalidateLayout() - { - if (const auto window = GetWindow()) - window->WindowInvalidateLayout(); - } - - void Control::Measure(const Size& available_size, const AdditionalMeasureInfo& additional_info) - { - SetDesiredSize(OnMeasureCore(available_size, additional_info)); - } - - void Control::Layout(const Rect& rect, const AdditionalLayoutInfo& additional_info) - { - auto my_additional_info = additional_info; - my_additional_info.total_offset.x += rect.left; - my_additional_info.total_offset.y += rect.top; - position_cache_.lefttop_position_absolute.x = my_additional_info.total_offset.x; - position_cache_.lefttop_position_absolute.y = my_additional_info.total_offset.y; - - SetRect(rect); - OnLayoutCore(Rect(Point::Zero(), rect.GetSize()), my_additional_info); - } - - Size Control::GetDesiredSize() const - { - return desired_size_; - } - - void Control::SetDesiredSize(const Size& desired_size) - { - desired_size_ = desired_size; - } - - inline void Shrink(Rect& rect, const Thickness& thickness) - { - rect.left += thickness.left; - rect.top += thickness.top; - rect.width -= thickness.GetHorizontalTotal(); - rect.height -= thickness.GetVerticalTotal(); - } - - Rect Control::GetRect(const RectRange range) - { - if (GetSize() == Size::Zero()) - return Rect(); - - const auto layout_params = GetLayoutParams(); - - auto result = Rect(Point::Zero(), GetSize()); - - if (range == RectRange::Margin) - return result; - - Shrink(result, layout_params->margin); - - if (range == RectRange::FullBorder) - return result; - - if (is_bordered_) - Shrink(result, Thickness(GetBorderProperty().GetStrokeWidth() / 2.0f)); - - if (range == RectRange::HalfBorder) - return result; - - if (is_bordered_) - Shrink(result, Thickness(GetBorderProperty().GetStrokeWidth() / 2.0f)); - - if (range == RectRange::Padding) - return result; - - Shrink(result, layout_params->padding); - - return result; - } - - Point Control::TransformPoint(const Point& point, const RectRange from, const RectRange to) - { - const auto rect_from = GetRect(from); - const auto rect_to = GetRect(to); - auto p = point; - p.x += rect_from.left; - p.y += rect_from.top; - p.x -= rect_to.left; - p.y -= rect_to.top; - return p; - } - - void Control::UpdateBorder() - { - RegenerateGeometryInfo(); - InvalidateLayout(); - InvalidateDraw(); - } - - void Control::SetBordered(const bool bordered) - { - if (bordered != is_bordered_) - { - is_bordered_ = bordered; - UpdateBorder(); - } - } - void Control::SetCursor(const Cursor::Ptr& cursor) { if (cursor != cursor_) @@ -444,289 +111,41 @@ namespace cru::ui } - void Control::OnInternalParentChanged(Control* old_internal_parent, Control* new_internal_parent) - { - - } - void Control::OnAttachToWindow(Window* window) { - window_ = window; - } - - void Control::OnDetachToWindow(Window * window) - { - window_ = nullptr; - } - - void Control::OnDrawDecoration(ID2D1DeviceContext* device_context) - { -#ifdef CRU_DEBUG_LAYOUT - if (GetWindow()->IsDebugLayout()) - { - if (padding_geometry_ != nullptr) - device_context->FillGeometry(padding_geometry_.Get(), UiManager::GetInstance()->GetPredefineResources()->debug_layout_padding_brush.Get()); - if (margin_geometry_ != nullptr) - device_context->FillGeometry(margin_geometry_.Get(), UiManager::GetInstance()->GetPredefineResources()->debug_layout_margin_brush.Get()); - device_context->DrawRectangle(Convert(GetRect(RectRange::Margin)), UiManager::GetInstance()->GetPredefineResources()->debug_layout_out_border_brush.Get()); - } -#endif - - if (is_bordered_ && geometry_info_.border_geometry != nullptr) - device_context->DrawGeometry( - geometry_info_.border_geometry.Get(), - GetBorderProperty().GetBrush().Get(), - GetBorderProperty().GetStrokeWidth(), - GetBorderProperty().GetStrokeStyle().Get() - ); - } - - void Control::OnDrawCore(ID2D1DeviceContext* device_context) - { - const auto ground_geometry = geometry_info_.padding_content_geometry; - //draw background. - if (ground_geometry != nullptr && background_brush_ != nullptr) - device_context->FillGeometry(ground_geometry.Get(), background_brush_.Get()); - const auto padding_rect = GetRect(RectRange::Padding); - graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(padding_rect.left, padding_rect.top), - [this](ID2D1DeviceContext* device_context) - { - events::DrawEventArgs args(this, this, device_context); - draw_background_event.Raise(args); - }); - - - const auto rect = GetRect(RectRange::Content); - graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(rect.left, rect.top), - [this](ID2D1DeviceContext* device_context) - { - events::DrawEventArgs args(this, this, device_context); - draw_content_event.Raise(args); - }); - - - //draw foreground. - if (ground_geometry != nullptr && foreground_brush_ != nullptr) - device_context->FillGeometry(ground_geometry.Get(), foreground_brush_.Get()); - graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(padding_rect.left, padding_rect.top), - [this](ID2D1DeviceContext* device_context) - { - events::DrawEventArgs args(this, this, device_context); - draw_foreground_event.Raise(args); - }); - } - - void Control::OnRectChange(const Rect& old_rect, const Rect& new_rect) - { } - void Control::RegenerateGeometryInfo() + void Control::OnDetachToWindow(Window * window) { - if (IsBordered()) - { - const auto bound_rect = GetRect(RectRange::HalfBorder); - const auto bound_rounded_rect = D2D1::RoundedRect(Convert(bound_rect), - GetBorderProperty().GetRadiusX(), - GetBorderProperty().GetRadiusY()); - - Microsoft::WRL::ComPtr<ID2D1RoundedRectangleGeometry> geometry; - ThrowIfFailed( - graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRoundedRectangleGeometry(bound_rounded_rect, &geometry) - ); - geometry_info_.border_geometry = std::move(geometry); - - const auto padding_rect = GetRect(RectRange::Padding); - const auto in_border_rounded_rect = D2D1::RoundedRect(Convert(padding_rect), - GetBorderProperty().GetRadiusX() - GetBorderProperty().GetStrokeWidth() / 2.0f, - GetBorderProperty().GetRadiusY() - GetBorderProperty().GetStrokeWidth() / 2.0f); - - Microsoft::WRL::ComPtr<ID2D1RoundedRectangleGeometry> geometry2; - ThrowIfFailed( - graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRoundedRectangleGeometry(in_border_rounded_rect, &geometry2) - ); - geometry_info_.padding_content_geometry = geometry2; - - - Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> geometry3; - ThrowIfFailed( - graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRectangleGeometry(Convert(GetRect(RectRange::Content)), &geometry3) - ); - Microsoft::WRL::ComPtr<ID2D1PathGeometry> geometry4; - ThrowIfFailed( - graph::GraphManager::GetInstance()->GetD2D1Factory()->CreatePathGeometry(&geometry4) - ); - Microsoft::WRL::ComPtr<ID2D1GeometrySink> sink; - geometry4->Open(&sink); - ThrowIfFailed( - geometry3->CombineWithGeometry(geometry2.Get(), D2D1_COMBINE_MODE_INTERSECT, D2D1::Matrix3x2F::Identity(), sink.Get()) - ); - sink->Close(); - geometry_info_.content_geometry = std::move(geometry4); - } - else - { - const auto bound_rect = GetRect(RectRange::Padding); - Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> geometry; - ThrowIfFailed( - graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRectangleGeometry(Convert(bound_rect), &geometry) - ); - geometry_info_.border_geometry = geometry; - geometry_info_.padding_content_geometry = std::move(geometry); - - Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> geometry2; - ThrowIfFailed( - graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRectangleGeometry(Convert(GetRect(RectRange::Content)), &geometry2) - ); - geometry_info_.content_geometry = std::move(geometry2); - } - //TODO: generate debug geometry -#ifdef CRU_DEBUG_LAYOUT - margin_geometry_ = CalculateSquareRingGeometry(GetRect(RectRange::Margin), GetRect(RectRange::FullBorder)); - padding_geometry_ = CalculateSquareRingGeometry(GetRect(RectRange::Padding), GetRect(RectRange::Content)); -#endif } void Control::OnMouseClickBegin(MouseButton button) { - } - void Control::OnMouseClickEnd(MouseButton button) - { } - inline Size ThicknessToSize(const Thickness& thickness) - { - return Size(thickness.left + thickness.right, thickness.top + thickness.bottom); - } - - Size Control::OnMeasureCore(const Size& available_size, const AdditionalMeasureInfo& additional_info) + void Control::OnMouseClickEnd(MouseButton button) { - const auto layout_params = GetLayoutParams(); - - if (!layout_params->Validate()) - throw std::runtime_error("LayoutParams is not valid. Please check it."); - - auto my_additional_info = additional_info; - - if (layout_params->width.mode == MeasureMode::Content) - my_additional_info.horizontal_stretchable = false; - else if (layout_params->width.mode == MeasureMode::Exactly) - my_additional_info.horizontal_stretchable = true; - // if stretch, then inherent parent's value - - if (layout_params->height.mode == MeasureMode::Content) - my_additional_info.vertical_stretchable = false; - else if (layout_params->height.mode == MeasureMode::Exactly) - my_additional_info.vertical_stretchable = true; - // if stretch, then inherent parent's value - - auto border_size = Size::Zero(); - if (is_bordered_) - { - const auto border_width = GetBorderProperty().GetStrokeWidth(); - border_size = Size(border_width * 2.0f, border_width * 2.0f); - } - - // the total size of padding, border and margin - const auto outer_size = ThicknessToSize(layout_params->padding) + - ThicknessToSize(layout_params->margin) + border_size; - - - auto&& get_content_measure_length = [](const LayoutSideParams& layout_length, const float available_length, const float outer_length) -> float - { - float length; - if (layout_length.mode == MeasureMode::Exactly) - length = layout_length.length; - else if (available_length > outer_length) - length = available_length - outer_length; - else - length = 0; - return Coerce(length, layout_length.min, layout_length.max); - }; - - // if padding, margin and border exceeded, then content size is 0. - const auto content_measure_size = Size( - get_content_measure_length(layout_params->width, available_size.width, outer_size.width), - get_content_measure_length(layout_params->height, available_size.height, outer_size.height) - ); - - const auto content_actual_size = OnMeasureContent(content_measure_size, my_additional_info); - - - - auto&& calculate_final_length = [](const bool stretch, const std::optional<float> min_length, const float measure_length, const float actual_length) -> float - { - // only use measure length when stretch and actual length is smaller than measure length, that is "stretch" - if (stretch && actual_length < measure_length) - return measure_length; - return Coerce(actual_length, min_length, std::nullopt); - }; - - const auto final_size = Size( - calculate_final_length(my_additional_info.horizontal_stretchable, layout_params->width.min, content_measure_size.width, content_actual_size.width), - calculate_final_length(my_additional_info.vertical_stretchable, layout_params->height.min, content_measure_size.height, content_actual_size.height) - ) + outer_size; - - return final_size; } - void Control::OnLayoutCore(const Rect& rect, const AdditionalLayoutInfo& additional_info) - { - const auto layout_params = GetLayoutParams(); - - auto border_width = 0.0f; - if (is_bordered_) - { - border_width = GetBorderProperty().GetStrokeWidth(); - } - - const Rect content_rect( - rect.left + layout_params->padding.left + layout_params->margin.right + border_width, - rect.top + layout_params->padding.top + layout_params->margin.top + border_width, - rect.width - layout_params->padding.GetHorizontalTotal() - layout_params->margin.GetHorizontalTotal() - border_width * 2.0f, - rect.height - layout_params->padding.GetVerticalTotal() - layout_params->margin.GetVerticalTotal() - border_width * 2.0f - ); - - if (content_rect.width < 0.0) - throw std::runtime_error(Format("Width to layout must sufficient. But in {}, width for content is {}.", ToUtf8String(GetControlType()), content_rect.width)); - if (content_rect.height < 0.0) - throw std::runtime_error(Format("Height to layout must sufficient. But in {}, height for content is {}.", ToUtf8String(GetControlType()), content_rect.height)); - - OnLayoutContent(content_rect, additional_info); - } const std::vector<Control*> NoChildControl::empty_control_vector{}; - std::list<Control*> GetAncestorList(Control* control) - { - std::list<Control*> l; - while (control != nullptr) - { - l.push_front(control); - control = control->GetInternalParent(); - } - return l; - } - - void NoChildControl::OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info) - { - } - - SingleChildControl::SingleChildControl() : child_vector_{nullptr}, child_(child_vector_[0]) + ContentControl::ContentControl() : child_vector_{nullptr}, child_(child_vector_[0]) { } - SingleChildControl::~SingleChildControl() + ContentControl::~ContentControl() { delete child_; } - void SingleChildControl::SetChild(Control* child) + void ContentControl::SetChild(Control* child) { if (child == child_) return; @@ -736,66 +155,25 @@ namespace cru::ui child_ = child; if (old_child) { - old_child->SetInternalParent(nullptr); + old_child->SetParent(nullptr); old_child->SetDescendantWindow(nullptr); } if (child) { - child->SetInternalParent(this); + child->SetParent(this); child->SetDescendantWindow(window); } OnChildChanged(old_child, child); } - void SingleChildControl::OnChildChanged(Control* old_child, Control* new_child) + void ContentControl::OnChildChanged(Control* old_child, Control* new_child) { } - Size SingleChildControl::OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo& additional_info) + void ControlAddChildCheck(Control* control) { - auto child_size = Size::Zero(); - if (child_) - { - child_->Measure(available_size, additional_info); - child_size = child_->GetDesiredSize(); - } - - return child_size; - } - - void SingleChildControl::OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info) - { - if (child_) - { - const auto layout_params = child_->GetLayoutParams(); - const auto size = child_->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(); - } - }; - - child_->Layout(Rect(Point( - 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), additional_info); - } - } - - void AddChildCheck(Control* control) - { - if (control->GetInternalParent() != nullptr) + if (control->GetParent() != nullptr) throw std::invalid_argument("The control already has a parent."); if (dynamic_cast<Window*>(control)) @@ -810,11 +188,11 @@ namespace cru::ui void MultiChildControl::AddChild(Control* control) { - AddChildCheck(control); + ControlAddChildCheck(control); children_.push_back(control); - control->SetInternalParent(this); + control->SetParent(this); control->SetDescendantWindow(GetWindow()); OnAddChild(control); @@ -822,14 +200,14 @@ namespace cru::ui void MultiChildControl::AddChild(Control* control, const int position) { - AddChildCheck(control); + ControlAddChildCheck(control); if (position < 0 || static_cast<decltype(children_.size())>(position) > this->children_.size()) throw std::invalid_argument("The position is out of range."); children_.insert(this->children_.cbegin() + position, control); - control->SetInternalParent(this); + control->SetParent(this); control->SetDescendantWindow(GetWindow()); OnAddChild(control); @@ -843,7 +221,7 @@ namespace cru::ui children_.erase(i); - child->SetInternalParent(nullptr); + child->SetParent(nullptr); child->SetDescendantWindow(nullptr); OnRemoveChild(child); @@ -859,7 +237,7 @@ namespace cru::ui children_.erase(i); - child->SetInternalParent(nullptr); + child->SetParent(nullptr); child->SetDescendantWindow(nullptr); OnRemoveChild(child); @@ -875,6 +253,17 @@ namespace cru::ui } + std::list<Control*> GetAncestorList(Control* control) + { + std::list<Control*> l; + while (control != nullptr) + { + l.push_front(control); + control = control->GetParent(); + } + return l; + } + Control* FindLowestCommonAncestor(Control * left, Control * right) { if (left == nullptr || right == nullptr) diff --git a/src/ui/control.hpp b/src/ui/control.hpp index 6abcc365..8e69fb07 100644 --- a/src/ui/control.hpp +++ b/src/ui/control.hpp @@ -12,49 +12,21 @@ #include "ui_base.hpp" #include "layout_base.hpp" #include "events/ui_event.hpp" -#include "border_property.hpp" #include "cursor.hpp" #include "any_map.hpp" +#include "input_util.hpp" namespace cru::ui { class Window; - struct AdditionalMeasureInfo - { - bool horizontal_stretchable = true; - bool vertical_stretchable = true; - }; - - struct AdditionalLayoutInfo - { - Point total_offset = Point::Zero(); - }; - - //the position cache - struct ControlPositionCache - { - //The lefttop relative to the ancestor. - Point lefttop_position_absolute = Point::Zero(); - }; - - class Control : public Object { friend class Window; protected: - struct GeometryInfo - { - Microsoft::WRL::ComPtr<ID2D1Geometry> border_geometry = nullptr; - Microsoft::WRL::ComPtr<ID2D1Geometry> padding_content_geometry = nullptr; - Microsoft::WRL::ComPtr<ID2D1Geometry> content_geometry = nullptr; - }; - - - protected: - Control(); + Control() = default; public: Control(const Control& other) = delete; Control(Control&& other) = delete; @@ -63,53 +35,35 @@ namespace cru::ui ~Control() override = default; public: - - //*************** region: tree *************** virtual StringView GetControlType() const = 0; - virtual const std::vector<Control*>& GetInternalChildren() const = 0; - - Control* GetParent() const - { - return parent_ == nullptr ? internal_parent_ : parent_; - } - - Control* GetInternalParent() const - { - return internal_parent_; - } + //*************** region: tree *************** + public: //Get the window if attached, otherwise, return nullptr. Window* GetWindow() const { return window_; } - void SetParent(Control* parent); + Control* GetParent() const + { + return parent_; + } - void SetInternalParent(Control* internal_parent); + void SetParent(Control* parent); void SetDescendantWindow(Window* window); - //Traverse the tree rooted the control including itself. void TraverseDescendants(const std::function<void(Control*)>& predicate); - //*************** region: position and size *************** - - //Get the lefttop relative to its parent. - virtual Point GetOffset(); - - //Get the actual size. - virtual Size GetSize(); - - // If offset changes, call RefreshDescendantPositionCache. - virtual void SetRect(const Rect& rect); + private: + static void TraverseDescendantsInternal(Control* control, const std::function<void(Control*)>& predicate); - //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; + //*************** region: position *************** + public: + virtual Point GetPositionInWindow() const = 0; //Local point to absolute point. Point ControlToWindow(const Point& point) const; @@ -117,122 +71,19 @@ namespace cru::ui //Absolute point to local point. Point WindowToControl(const Point& point) const; - void RefreshDescendantPositionCache(); - - private: - static void RefreshControlPositionCacheInternal(Control* control, const Point& parent_lefttop_absolute); - - public: - - // Default implement in Control is test point in border geometry's - // fill and stroke with width of border. - virtual bool IsPointInside(const Point& point); - - // Get the top control among all descendants (including self) in local coordinate. - virtual Control* HitTest(const Point& point); - - //*************** region: graphic *************** - - bool IsClipContent() const - { - return clip_content_; - } - - void SetClipContent(bool clip); - - //Draw this control and its child controls. - void Draw(ID2D1DeviceContext* device_context); - - virtual void InvalidateDraw(); - - Microsoft::WRL::ComPtr<ID2D1Brush> GetForegroundBrush() const - { - return foreground_brush_; - } - - void SetForegroundBrush(Microsoft::WRL::ComPtr<ID2D1Brush> foreground_brush) - { - foreground_brush_ = std::move(foreground_brush); - InvalidateDraw(); - } - - Microsoft::WRL::ComPtr<ID2D1Brush> GetBackgroundBrush() const - { - return background_brush_; - } - - void SetBackgroundBrush(Microsoft::WRL::ComPtr<ID2D1Brush> background_brush) - { - background_brush_ = std::move(background_brush); - InvalidateDraw(); - } //*************** region: focus *************** - + public: 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, const AdditionalMeasureInfo& additional_info); - - void Layout(const Rect& rect, const AdditionalLayoutInfo& additional_info); - - 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 UpdateBorder(); - - 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. - + public: Cursor::Ptr GetCursor() const { return cursor_; @@ -241,7 +92,16 @@ namespace cru::ui void SetCursor(const Cursor::Ptr& cursor); + //*************** region: additional properties *************** + public: + AnyMap* GetAdditionalPropertyMap() + { + return &additional_property_map_; + } + + //*************** region: events *************** + public: //Raised when mouse enter the control. events::RoutedEvent<events::MouseEventArgs> mouse_enter_event; //Raised when mouse is leave the control. @@ -264,95 +124,29 @@ namespace cru::ui events::RoutedEvent<events::FocusChangeEventArgs> get_focus_event; events::RoutedEvent<events::FocusChangeEventArgs> lose_focus_event; - Event<events::DrawEventArgs> draw_content_event; - Event<events::DrawEventArgs> draw_background_event; - Event<events::DrawEventArgs> draw_foreground_event; - - //*************** region: tree event *************** + //*************** region: tree *************** protected: - virtual void OnParentChanged(Control* old_parent, Control* new_parent); - - virtual void OnInternalParentChanged(Control* old_internal_parent, Control* new_internal_parent); + virtual const std::vector<Control*>& GetInternalChildren() const = 0; - //Invoked when the control is attached to a window. Overrides should invoke base. + virtual void OnParentChanged(Control* old_parent, Control* new_parent); virtual void OnAttachToWindow(Window* window); - //Invoked when the control is detached to a window. Overrides should invoke base. virtual void OnDetachToWindow(Window* window); - //*************** region: graphic event *************** - private: - void OnDrawDecoration(ID2D1DeviceContext* device_context); - void OnDrawCore(ID2D1DeviceContext* device_context); - - - //*************** region: position and size event *************** - protected: - virtual void OnRectChange(const Rect& old_rect, const Rect& new_rect); - - void RegenerateGeometryInfo(); - - const GeometryInfo& GetGeometryInfo() const - { - return geometry_info_; - } - - - //*************** region: mouse event *************** + //*************** region: additional mouse event *************** protected: virtual void OnMouseClickBegin(MouseButton button); virtual void OnMouseClickEnd(MouseButton button); - //*************** region: layout *************** - private: - Size OnMeasureCore(const Size& available_size, const AdditionalMeasureInfo& additional_info); - void OnLayoutCore(const Rect& rect, const AdditionalLayoutInfo& additional_info); - - protected: - virtual Size OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo& additional_info) = 0; - virtual void OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info) = 0; - private: Window * window_ = nullptr; - Control* parent_ = nullptr; // when parent and internal parent are the same, parent_ is nullptr. - Control * internal_parent_ = nullptr; - - Rect rect_{}; - - ControlPositionCache position_cache_{}; - - 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_; - - GeometryInfo geometry_info_{}; - - bool clip_content_ = false; + Control* parent_ = nullptr; - Microsoft::WRL::ComPtr<ID2D1Brush> foreground_brush_ = nullptr; - Microsoft::WRL::ComPtr<ID2D1Brush> background_brush_ = nullptr; + Cursor::Ptr cursor_{}; 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_{}; }; @@ -372,31 +166,24 @@ namespace cru::ui NoChildControl& operator=(NoChildControl&& other) = delete; ~NoChildControl() override = default; + protected: const std::vector<Control*>& GetInternalChildren() const override final { return empty_control_vector; } - - protected: - void OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info) override; }; - class SingleChildControl : public Control + class ContentControl : public Control { protected: - SingleChildControl(); + ContentControl(); public: - SingleChildControl(const SingleChildControl& other) = delete; - SingleChildControl(SingleChildControl&& other) = delete; - SingleChildControl& operator=(const SingleChildControl& other) = delete; - SingleChildControl& operator=(SingleChildControl&& other) = delete; - ~SingleChildControl() override; - - const std::vector<Control*>& GetInternalChildren() const override final - { - return child_vector_; - } + ContentControl(const ContentControl& other) = delete; + ContentControl(ContentControl&& other) = delete; + ContentControl& operator=(const ContentControl& other) = delete; + ContentControl& operator=(ContentControl&& other) = delete; + ~ContentControl() override; Control* GetChild() const { @@ -406,12 +193,14 @@ namespace cru::ui void SetChild(Control* child); protected: + const std::vector<Control*>& GetInternalChildren() const override final + { + return child_vector_; + } + // Override should call base. virtual void OnChildChanged(Control* old_child, Control* new_child); - Size OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo& additional_info) override; - void OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info) override; - private: std::vector<Control*> child_vector_; Control*& child_; @@ -452,9 +241,7 @@ namespace cru::ui void RemoveChild(int position); 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); private: diff --git a/src/ui/controls/button.hpp b/src/ui/controls/button.hpp index 82694fe8..6436f7c0 100644 --- a/src/ui/controls/button.hpp +++ b/src/ui/controls/button.hpp @@ -9,7 +9,7 @@ namespace cru::ui::controls { - class Button : public SingleChildControl + class Button : public ContentControl { public: static constexpr auto control_type = L"Button"; diff --git a/src/ui/controls/list_item.hpp b/src/ui/controls/list_item.hpp index a50b2496..bf8f8d8e 100644 --- a/src/ui/controls/list_item.hpp +++ b/src/ui/controls/list_item.hpp @@ -10,7 +10,7 @@ namespace cru::ui::controls { - class ListItem : public SingleChildControl + class ListItem : public ContentControl { public: static constexpr auto control_type = L"ListItem"; diff --git a/src/ui/controls/scroll_control.hpp b/src/ui/controls/scroll_control.hpp index 7138add6..84ebca30 100644 --- a/src/ui/controls/scroll_control.hpp +++ b/src/ui/controls/scroll_control.hpp @@ -17,7 +17,7 @@ namespace cru::ui::controls // Done: API // Done: ScrollBar // Done: MouseEvent - class ScrollControl : public SingleChildControl + class ScrollControl : public ContentControl { private: struct ScrollBarInfo diff --git a/src/ui/render/linear_layout_render_object.cpp b/src/ui/render/linear_layout_render_object.cpp new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/ui/render/linear_layout_render_object.cpp diff --git a/src/ui/render/linear_layout_render_object.hpp b/src/ui/render/linear_layout_render_object.hpp new file mode 100644 index 00000000..6f70f09b --- /dev/null +++ b/src/ui/render/linear_layout_render_object.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/src/ui/render/render_object.cpp b/src/ui/render/render_object.cpp index 0828fc9c..c2aaeb62 100644 --- a/src/ui/render/render_object.cpp +++ b/src/ui/render/render_object.cpp @@ -3,96 +3,88 @@ namespace cru::ui::render { - void RenderObject::SetRenderHost(IRenderHost* new_render_host) - { - if (new_render_host == render_host_) - return; - - const auto old = render_host_; - render_host_ = new_render_host; - OnRenderHostChanged(old, new_render_host); - } - - void RenderObject::OnRenderHostChanged(IRenderHost* old_render_host, IRenderHost* new_render_host) - { - - } - - void RenderObject::InvalidateRenderHost() - { - if (render_host_ != nullptr) - render_host_->InvalidateRender(); - } - - void StrokeRenderObject::SetStrokeWidth(const float new_stroke_width) - { - if (stroke_width_ == new_stroke_width) - return; - - stroke_width_ = new_stroke_width; - InvalidateRenderHost(); - } - - void StrokeRenderObject::SetBrush(Microsoft::WRL::ComPtr<ID2D1Brush> new_brush) - { - if (brush_ == new_brush) - return; - - brush_ = std::move(new_brush); - InvalidateRenderHost(); - } - - void StrokeRenderObject::SetStrokeStyle(Microsoft::WRL::ComPtr<ID2D1StrokeStyle> new_stroke_style) - { - if (stroke_style_ == new_stroke_style) - return; - - stroke_style_ = std::move(new_stroke_style); - InvalidateRenderHost(); - } - - void FillRenderObject::SetBrush(Microsoft::WRL::ComPtr<ID2D1Brush> new_brush) - { - if (brush_ == new_brush) - return; - - brush_ = std::move(new_brush); - InvalidateRenderHost(); - } - - namespace details - { - template class ShapeRenderObject<Rect>; - template class ShapeRenderObject<RoundedRect>; - template class ShapeRenderObject<Ellipse>; - } - - namespace details - { - template ShapeStrokeRenderObject<Rect, D2D1_RECT_F, &ID2D1RenderTarget::DrawRectangle>; - template ShapeStrokeRenderObject<RoundedRect, D2D1_ROUNDED_RECT, &ID2D1RenderTarget::DrawRoundedRectangle>; - template ShapeStrokeRenderObject<Ellipse, D2D1_ELLIPSE, &ID2D1RenderTarget::DrawEllipse>; - } - - namespace details - { - template ShapeFillRenderObject<Rect, D2D1_RECT_F, &ID2D1RenderTarget::FillRectangle>; - template ShapeFillRenderObject<RoundedRect, D2D1_ROUNDED_RECT, &ID2D1RenderTarget::FillRoundedRectangle>; - template ShapeFillRenderObject<Ellipse, D2D1_ELLIPSE, &ID2D1RenderTarget::FillEllipse>; - } - - void CustomDrawHandlerRenderObject::SetDrawHandler(DrawHandler new_draw_handler) - { - if (draw_handler_ == nullptr && new_draw_handler == nullptr) - return; - - draw_handler_ = std::move(new_draw_handler); - InvalidateRenderHost(); - } - - void CustomDrawHandlerRenderObject::Draw(ID2D1RenderTarget* render_target) - { - if (draw_handler_ != nullptr) - draw_handler_(render_target); - } +void RenderObject::SetRenderHost(IRenderHost* new_render_host) +{ + if (new_render_host == render_host_) return; + + const auto old = render_host_; + render_host_ = new_render_host; + OnRenderHostChanged(old, new_render_host); +} + +void RenderObject::AddChild(RenderObject* render_object, const int position) +{ + if (render_object->GetParent() != nullptr) + throw std::invalid_argument("Render object already has a parent."); + + if (position < 0) + throw std::invalid_argument("Position index is less than 0."); + + if (static_cast<std::vector<RenderObject*>::size_type>(position) > + children_.size()) + throw std::invalid_argument("Position index is out of bound."); + + children_.insert(children_.cbegin() + position, render_object); + render_object->SetParent(this); + OnAddChild(render_object, position); +} + +void RenderObject::RemoveChild(const int position) +{ + if (position < 0) + throw std::invalid_argument("Position index is less than 0."); + + if (static_cast<std::vector<RenderObject*>::size_type>(position) >= + children_.size()) + throw std::invalid_argument("Position index is out of bound."); + + const auto i = children_.cbegin() + position; + const auto removed_child = *i; + children_.erase(i); + removed_child->SetParent(nullptr); + OnRemoveChild(removed_child, position); +} + + +void RenderObject::OnRenderHostChanged(IRenderHost* old_render_host, + IRenderHost* new_render_host) +{ +} + +void RenderObject::InvalidateRenderHostPaint() const +{ + if (render_host_ != nullptr) render_host_->InvalidatePaint(); +} + +void RenderObject::InvalidateRenderHostLayout() const +{ + if (render_host_ != nullptr) render_host_->InvalidateLayout(); +} + +void RenderObject::OnParentChanged(RenderObject* old_parent, + RenderObject* new_parent) +{ +} + + +void RenderObject::OnAddChild(RenderObject* new_child, int position) +{ +} + +void RenderObject::OnRemoveChild(RenderObject* removed_child, int position) +{ +} + +void RenderObject::SetParent(RenderObject* new_parent) +{ + const auto old_parent = parent_; + parent_ = new_parent; + OnParentChanged(old_parent, new_parent); +} + + +void LinearLayoutRenderObject::Measure(const MeasureConstraint& constraint) +{ + } +} // namespace cru::ui::render diff --git a/src/ui/render/render_object.hpp b/src/ui/render/render_object.hpp index 31745be5..00f761d1 100644 --- a/src/ui/render/render_object.hpp +++ b/src/ui/render/render_object.hpp @@ -2,256 +2,108 @@ #include "pre.hpp" +#include <optional> +#include <vector> #include "system_headers.hpp" -#include <functional> -#include <cassert> #include "base.hpp" #include "ui/ui_base.hpp" -#include "ui/d2d_util.hpp" namespace cru::ui::render { - /* About Render Object - * - * Render object is a concrete subclass of RenderObject class. - * It represents a painting action on a d2d render target. By - * overriding "Draw" virtual method, it can customize its painting - * action. - */ - - - struct IRenderHost : Interface - { - virtual void InvalidateRender() = 0; - }; - - - class RenderObject : public Object - { - protected: - RenderObject() = default; - public: - RenderObject(const RenderObject& other) = delete; - RenderObject(RenderObject&& other) = delete; - RenderObject& operator=(const RenderObject& other) = delete; - RenderObject& operator=(RenderObject&& other) = delete; - ~RenderObject() override = default; - - virtual void Draw(ID2D1RenderTarget* render_target) = 0; - - IRenderHost* GetRenderHost() const - { - return render_host_; - } - - void SetRenderHost(IRenderHost* new_render_host); - - protected: - virtual void OnRenderHostChanged(IRenderHost* old_render_host, IRenderHost* new_render_host); - - void InvalidateRenderHost(); - - private: - IRenderHost* render_host_ = nullptr; - }; - - - class StrokeRenderObject : public virtual RenderObject - { - protected: - StrokeRenderObject() = default; - public: - StrokeRenderObject(const StrokeRenderObject& other) = delete; - StrokeRenderObject(StrokeRenderObject&& other) = delete; - StrokeRenderObject& operator=(const StrokeRenderObject& other) = delete; - StrokeRenderObject& operator=(StrokeRenderObject&& other) = delete; - ~StrokeRenderObject() override = default; - - float GetStrokeWidth() const - { - return stroke_width_; - } - - void SetStrokeWidth(float new_stroke_width); - - Microsoft::WRL::ComPtr<ID2D1Brush> GetBrush() const - { - return brush_; - } - - void SetBrush(Microsoft::WRL::ComPtr<ID2D1Brush> new_brush); - - Microsoft::WRL::ComPtr<ID2D1StrokeStyle> GetStrokeStyle() const - { - return stroke_style_; - } - - void SetStrokeStyle(Microsoft::WRL::ComPtr<ID2D1StrokeStyle> new_stroke_style); - - private: - float stroke_width_ = 1.0f; - Microsoft::WRL::ComPtr<ID2D1Brush> brush_ = nullptr; - Microsoft::WRL::ComPtr<ID2D1StrokeStyle> stroke_style_ = nullptr; - }; - - - class FillRenderObject : public virtual RenderObject - { - protected: - FillRenderObject() = default; - public: - FillRenderObject(const FillRenderObject& other) = delete; - FillRenderObject(FillRenderObject&& other) = delete; - FillRenderObject& operator=(const FillRenderObject& other) = delete; - FillRenderObject& operator=(FillRenderObject&& other) = delete; - ~FillRenderObject() override = default; - - Microsoft::WRL::ComPtr<ID2D1Brush> GetBrush() const - { - return brush_; - } - - void SetBrush(Microsoft::WRL::ComPtr<ID2D1Brush> new_brush); - - private: - Microsoft::WRL::ComPtr<ID2D1Brush> brush_ = nullptr; - }; - - - namespace details - { - template <typename TShapeType> - class ShapeRenderObject : public virtual RenderObject - { - public: - using ShapeType = TShapeType; - protected: - ShapeRenderObject() = default; - public: - ShapeRenderObject(const ShapeRenderObject& other) = delete; - ShapeRenderObject& operator=(const ShapeRenderObject& other) = delete; - ShapeRenderObject(ShapeRenderObject&& other) = delete; - ShapeRenderObject& operator=(ShapeRenderObject&& other) = delete; - ~ShapeRenderObject() override = default; - - ShapeType GetShape() const - { - return shape_; - } - - void SetShape(const ShapeType& new_shape) - { - if (new_shape == shape_) - return; - - shape_ = new_shape; - InvalidateRenderHost(); - } - - private: - ShapeType shape_; - }; +struct MeasureConstraint +{ + std::optional<float> min_width; + std::optional<float> min_height; + std::optional<float> max_width; + std::optional<float> max_height; +}; - extern template class ShapeRenderObject<Rect>; - extern template class ShapeRenderObject<RoundedRect>; - extern template class ShapeRenderObject<Ellipse>; - } +struct LayoutConstraint +{ + float preferred_width; + float preferred_height; +}; - using RectangleRenderObject = details::ShapeRenderObject<Rect>; - using RoundedRectangleRenderObject = details::ShapeRenderObject<RoundedRect>; - using EllipseRenderObject = details::ShapeRenderObject<Ellipse>; +struct IRenderHost : Interface +{ + virtual void InvalidatePaint() = 0; + virtual void InvalidateLayout() = 0; +}; - namespace details - { - template<typename TShapeType, typename TD2D1ShapeType, void (ID2D1RenderTarget::*draw_function)(const TD2D1ShapeType&, ID2D1Brush*, float, ID2D1StrokeStyle*)> - class ShapeStrokeRenderObject : public ShapeRenderObject<TShapeType>, public StrokeRenderObject - { - public: - ShapeStrokeRenderObject() = default; - ShapeStrokeRenderObject(const ShapeStrokeRenderObject& other) = delete; - ShapeStrokeRenderObject& operator=(const ShapeStrokeRenderObject& other) = delete; - ShapeStrokeRenderObject(ShapeStrokeRenderObject&& other) = delete; - ShapeStrokeRenderObject& operator=(ShapeStrokeRenderObject&& other) = delete; - ~ShapeStrokeRenderObject() = default; +// features: +// 1. tree +// 2. layout +// 3. paint +// 3. hit test +class RenderObject : public Object +{ +protected: + RenderObject() = default; - protected: - void Draw(ID2D1RenderTarget* render_target) override - { - const auto brush = GetBrush(); - if (brush != nullptr) - (render_target->*draw_function)(Convert(GetShape()), brush.Get(), GetStrokeWidth(), GetStrokeStyle().Get()); - } - }; +public: + RenderObject(const RenderObject& other) = delete; + RenderObject(RenderObject&& other) = delete; + RenderObject& operator=(const RenderObject& other) = delete; + RenderObject& operator=(RenderObject&& other) = delete; + ~RenderObject() override = default; - extern template ShapeStrokeRenderObject<Rect, D2D1_RECT_F, &ID2D1RenderTarget::DrawRectangle>; - extern template ShapeStrokeRenderObject<RoundedRect, D2D1_ROUNDED_RECT, &ID2D1RenderTarget::DrawRoundedRectangle>; - extern template ShapeStrokeRenderObject<Ellipse, D2D1_ELLIPSE, &ID2D1RenderTarget::DrawEllipse>; - } + IRenderHost* GetRenderHost() const { return render_host_; } + void SetRenderHost(IRenderHost* new_render_host); - using RectangleStrokeRenderObject = details::ShapeStrokeRenderObject<Rect, D2D1_RECT_F, &ID2D1RenderTarget::DrawRectangle>; - using RoundedRectangleStrokeRenderObject = details::ShapeStrokeRenderObject<RoundedRect, D2D1_ROUNDED_RECT, &ID2D1RenderTarget::DrawRoundedRectangle>; - using EllipseStrokeRenderObject = details::ShapeStrokeRenderObject<Ellipse, D2D1_ELLIPSE, &ID2D1RenderTarget::DrawEllipse>; + RenderObject* GetParent() const { return parent_; } + const std::vector<RenderObject*>& GetChildren() const { return children_; } + void AddChild(RenderObject* render_object, int position); + void RemoveChild(int position); - namespace details - { - template<typename TShapeType, typename TD2D1ShapeType, void (ID2D1RenderTarget::*fill_function)(const TD2D1ShapeType&, ID2D1Brush*)> - class ShapeFillRenderObject : public ShapeRenderObject<TShapeType>, public StrokeRenderObject - { - public: - ShapeFillRenderObject() = default; - ShapeFillRenderObject(const ShapeFillRenderObject& other) = delete; - ShapeFillRenderObject& operator=(const ShapeFillRenderObject& other) = delete; - ShapeFillRenderObject(ShapeFillRenderObject&& other) = delete; - ShapeFillRenderObject& operator=(ShapeFillRenderObject&& other) = delete; - ~ShapeFillRenderObject() = default; + virtual void Measure(const MeasureConstraint& constraint) = 0; + virtual void Layout(const LayoutConstraint& constraint) = 0; - protected: - void Draw(ID2D1RenderTarget* render_target) override - { - const auto brush = GetBrush(); - if (brush != nullptr) - (render_target->*fill_function)(Convert(GetShape()), brush.Get()); - } - }; + virtual void Draw(ID2D1RenderTarget* render_target) = 0; - extern template ShapeFillRenderObject<Rect, D2D1_RECT_F, &ID2D1RenderTarget::FillRectangle>; - extern template ShapeFillRenderObject<RoundedRect, D2D1_ROUNDED_RECT, &ID2D1RenderTarget::FillRoundedRectangle>; - extern template ShapeFillRenderObject<Ellipse, D2D1_ELLIPSE, &ID2D1RenderTarget::FillEllipse>; - } + virtual void HitTest(const Point& point) = 0; - using RectangleFillRenderObject = details::ShapeFillRenderObject<Rect, D2D1_RECT_F, &ID2D1RenderTarget::FillRectangle>; - using RoundedRectangleFillRenderObject = details::ShapeFillRenderObject<RoundedRect, D2D1_ROUNDED_RECT, &ID2D1RenderTarget::FillRoundedRectangle>; - using EllipseFillRenderObject = details::ShapeFillRenderObject<Ellipse, D2D1_ELLIPSE, &ID2D1RenderTarget::FillEllipse>; +protected: + virtual void OnRenderHostChanged(IRenderHost* old_render_host, + IRenderHost* new_render_host); + void InvalidateRenderHostPaint() const; + void InvalidateRenderHostLayout() const; - class CustomDrawHandlerRenderObject : public RenderObject - { - public: - using DrawHandler = std::function<void(ID2D1RenderTarget*)>; + virtual void OnParentChanged(RenderObject* old_parent, + RenderObject* new_parent); - CustomDrawHandlerRenderObject() = default; - CustomDrawHandlerRenderObject(const CustomDrawHandlerRenderObject& other) = delete; - CustomDrawHandlerRenderObject& operator=(const CustomDrawHandlerRenderObject& other) = delete; - CustomDrawHandlerRenderObject(CustomDrawHandlerRenderObject&& other) = delete; - CustomDrawHandlerRenderObject& operator=(CustomDrawHandlerRenderObject&& other) = delete; - ~CustomDrawHandlerRenderObject() override = default; + virtual void OnAddChild(RenderObject* new_child, int position); + virtual void OnRemoveChild(RenderObject* removed_child, int position); - DrawHandler GetDrawHandler() const - { - return draw_handler_; - } +private: + void SetParent(RenderObject* new_parent); - void SetDrawHandler(DrawHandler new_draw_handler); +private: + IRenderHost* render_host_ = nullptr; + RenderObject* parent_ = nullptr; + std::vector<RenderObject*> children_; +}; - protected: - void Draw(ID2D1RenderTarget* render_target) override; - private: - DrawHandler draw_handler_{}; - }; -} +class LinearLayoutRenderObject : public RenderObject +{ +public: + LinearLayoutRenderObject() = default; + LinearLayoutRenderObject(const LinearLayoutRenderObject& other) = delete; + LinearLayoutRenderObject& operator=(const LinearLayoutRenderObject& other) = + delete; + LinearLayoutRenderObject(LinearLayoutRenderObject&& other) = delete; + LinearLayoutRenderObject& operator=(LinearLayoutRenderObject&& other) = + delete; + ~LinearLayoutRenderObject() = default; + + void Measure(const MeasureConstraint& constraint) override; + +private: +}; +} // namespace cru::ui::render diff --git a/src/ui/ui_base.hpp b/src/ui/ui_base.hpp index c26bfe0e..17cb9acb 100644 --- a/src/ui/ui_base.hpp +++ b/src/ui/ui_base.hpp @@ -8,7 +8,7 @@ namespace cru::ui { - struct Point + struct Point final { constexpr static Point Zero() { @@ -33,7 +33,7 @@ namespace cru::ui } - struct Size + struct Size final { constexpr static Size Zero() { @@ -68,7 +68,7 @@ namespace cru::ui } - struct Thickness + struct Thickness final { constexpr static Thickness Zero() { @@ -122,7 +122,7 @@ namespace cru::ui float bottom; }; - constexpr bool operator == (const Thickness& left, const Thickness& right) + constexpr bool operator==(const Thickness& left, const Thickness& right) { return left.left == right.left && left.top == right.top && @@ -130,13 +130,13 @@ namespace cru::ui left.bottom == right.bottom; } - constexpr bool operator != (const Thickness& left, const Thickness& right) + constexpr bool operator!=(const Thickness& left, const Thickness& right) { return !(left == right); } - struct Rect + struct Rect final { constexpr Rect() = default; constexpr Rect(const float left, const float top, const float width, const float height) @@ -228,7 +228,7 @@ namespace cru::ui } - struct RoundedRect + struct RoundedRect final { constexpr RoundedRect() = default; constexpr RoundedRect(const Rect& rect, const float radius_x, const float radius_y) @@ -239,17 +239,18 @@ namespace cru::ui float radius_y = 0.0f; }; - constexpr bool operator == (const RoundedRect& left, const RoundedRect& right) + constexpr bool operator==(const RoundedRect& left, const RoundedRect& right) { return left.rect == right.rect && left.radius_x == right.radius_x && left.radius_y == right.radius_y; } - constexpr bool operator != (const RoundedRect& left, const RoundedRect& right) + constexpr bool operator!=(const RoundedRect& left, const RoundedRect& right) { return !(left == right); } - struct Ellipse + + struct Ellipse final { constexpr Ellipse() = default; constexpr Ellipse(const Point& center, const float radius_x, const float radius_y) @@ -270,26 +271,18 @@ namespace cru::ui float radius_y = 0.0f; }; - constexpr bool operator == (const Ellipse& left, const Ellipse& right) + constexpr bool operator==(const Ellipse& left, const Ellipse& right) { return left.center == right.center && left.radius_x == right.radius_x && left.radius_y == right.radius_y; } - constexpr bool operator != (const Ellipse& left, const Ellipse& right) + constexpr bool operator!=(const Ellipse& left, const Ellipse& right) { return !(left == right); } - enum class MouseButton - { - Left, - Right, - Middle - }; - - - struct TextRange + struct TextRange final { constexpr static std::optional<TextRange> FromTwoSides(unsigned first, unsigned second) { @@ -317,8 +310,4 @@ namespace cru::ui unsigned position = 0; unsigned count = 0; }; - - bool IsKeyDown(int virtual_code); - bool IsKeyToggled(int virtual_code); - bool IsAnyMouseButtonDown(); } diff --git a/src/ui/window.cpp b/src/ui/window.cpp index 86fa4436..51b3f628 100644 --- a/src/ui/window.cpp +++ b/src/ui/window.cpp @@ -7,29 +7,6 @@ namespace cru::ui { - WindowClass::WindowClass(const String& name, WNDPROC window_proc, HINSTANCE h_instance) - : name_(name) - { - WNDCLASSEX window_class; - window_class.cbSize = sizeof(WNDCLASSEX); - - window_class.style = CS_HREDRAW | CS_VREDRAW; - window_class.lpfnWndProc = window_proc; - window_class.cbClsExtra = 0; - window_class.cbWndExtra = 0; - window_class.hInstance = h_instance; - window_class.hIcon = LoadIcon(NULL, IDI_APPLICATION); - window_class.hCursor = LoadCursor(NULL, IDC_ARROW); - window_class.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); - window_class.lpszMenuName = NULL; - window_class.lpszClassName = name.c_str(); - window_class.hIconSm = NULL; - - atom_ = RegisterClassEx(&window_class); - if (atom_ == 0) - throw std::runtime_error("Failed to create window class."); - } - LRESULT __stdcall GeneralWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { auto window = WindowManager::GetInstance()->FromHandle(hWnd); diff --git a/src/ui/window.hpp b/src/ui/window.hpp index e96d4d92..d3374684 100644 --- a/src/ui/window.hpp +++ b/src/ui/window.hpp @@ -17,32 +17,6 @@ namespace cru::graph 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: @@ -85,7 +59,7 @@ namespace cru::ui - class Window final : public SingleChildControl + class Window final : public ContentControl { friend class WindowManager; public: diff --git a/src/ui/window_class.cpp b/src/ui/window_class.cpp new file mode 100644 index 00000000..456d9492 --- /dev/null +++ b/src/ui/window_class.cpp @@ -0,0 +1,25 @@ +#include "window_class.hpp" + +namespace cru::ui { +WindowClass::WindowClass(const String& name, WNDPROC window_proc, + HINSTANCE h_instance) + : name_(name) { + WNDCLASSEX window_class; + window_class.cbSize = sizeof(WNDCLASSEX); + + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.lpfnWndProc = window_proc; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = h_instance; + window_class.hIcon = LoadIcon(NULL, IDI_APPLICATION); + window_class.hCursor = LoadCursor(NULL, IDC_ARROW); + window_class.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); + window_class.lpszMenuName = NULL; + window_class.lpszClassName = name.c_str(); + window_class.hIconSm = NULL; + + atom_ = ::RegisterClassExW(&window_class); + if (atom_ == 0) throw std::runtime_error("Failed to create window class."); +} +} // namespace cru::ui diff --git a/src/ui/window_class.hpp b/src/ui/window_class.hpp new file mode 100644 index 00000000..66babd94 --- /dev/null +++ b/src/ui/window_class.hpp @@ -0,0 +1,26 @@ +#pragma once +#include "pre.hpp" + +#include "system_headers.hpp" + +#include "base.hpp" + +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_; +}; +} // namespace cru::ui |