diff options
Diffstat (limited to 'src/ui/control.cpp')
-rw-r--r-- | src/ui/control.cpp | 712 |
1 files changed, 712 insertions, 0 deletions
diff --git a/src/ui/control.cpp b/src/ui/control.cpp new file mode 100644 index 00000000..8aec8640 --- /dev/null +++ b/src/ui/control.cpp @@ -0,0 +1,712 @@ +#include "control.h" + +#include <string> +#include <algorithm> +#include <chrono> + +#include "window.h" +#include "timer.h" +#include "debug_base.h" + +namespace cru { + namespace ui { + using namespace events; + + Control::Control(const bool container) : + is_container_(container) + { + + } + + Control::Control(WindowConstructorTag, Window* window) : Control(true) + { + window_ = window; + } + + Control::~Control() + { + ForeachChild([](auto control) + { + delete control; + }); + } + + void Control::ForeachChild(Function<void(Control*)>&& predicate) const + { + if (is_container_) + for (const auto child : children_) + predicate(child); + } + + void Control::ForeachChild(Function<FlowControl(Control*)>&& predicate) const + { + if (is_container_) + for (const auto child : children_) + { + if (predicate(child) == FlowControl::Break) + break; + } + } + + void AddChildCheck(Control* control) + { + if (control->GetParent() != nullptr) + throw std::invalid_argument("The control already has a parent."); + + if (dynamic_cast<Window*>(control)) + throw std::invalid_argument("Can't add a window as child."); + } + + void Control::AddChild(Control* control) + { + ThrowIfNotContainer(); + AddChildCheck(control); + + this->children_.push_back(control); + + control->parent_ = this; + + this->OnAddChild(control); + } + + void Control::AddChild(Control* control, int position) + { + ThrowIfNotContainer(); + AddChildCheck(control); + + if (position < 0 || static_cast<decltype(this->children_.size())>(position) > this->children_.size()) + throw std::invalid_argument("The position is out of range."); + + this->children_.insert(this->children_.cbegin() + position, control); + + control->parent_ = this; + + this->OnAddChild(this); + } + + void Control::RemoveChild(Control* child) + { + ThrowIfNotContainer(); + const auto i = std::find(this->children_.cbegin(), this->children_.cend(), child); + if (i == this->children_.cend()) + throw std::invalid_argument("The argument child is not a child of this control."); + + this->children_.erase(i); + + child->parent_ = nullptr; + + this->OnRemoveChild(this); + } + + void Control::RemoveChild(const int position) + { + ThrowIfNotContainer(); + if (position < 0 || static_cast<decltype(this->children_.size())>(position) >= this->children_.size()) + throw std::invalid_argument("The position is out of range."); + + const auto p = children_.cbegin() + position; + const auto child = *p; + children_.erase(p); + + child->parent_ = nullptr; + + this->OnRemoveChild(child); + } + + Control* Control::GetAncestor() + { + // if attached to window, the window is the ancestor. + if (window_) + return window_; + + // otherwise find the ancestor + auto ancestor = this; + while (const auto parent = ancestor->GetParent()) + ancestor = parent; + return ancestor; + } + + void TraverseDescendantsInternal(Control* control, Function<void(Control*)>& predicate) + { + predicate(control); + control->ForeachChild([&predicate](Control* c) { + TraverseDescendantsInternal(c, predicate); + }); + } + + void Control::TraverseDescendants(Function<void(Control*)>&& predicate) + { + if (is_container_) + TraverseDescendantsInternal(this, predicate); + else + predicate(this); + } + + Point Control::GetPositionRelative() + { + return position_; + } + + void Control::SetPositionRelative(const Point & position) + { + if (position != position_) + { + if (old_position_ == position) // if cache has been refreshed and no pending notify + old_position_ = position_; + position_ = position; + LayoutManager::GetInstance()->InvalidateControlPositionCache(this); + if (auto window = GetWindow()) + { + window->Repaint(); + } + } + } + + Size Control::GetSize() + { + return size_; + } + + void Control::SetSize(const Size & size) + { + const auto old_size = size_; + size_ = size; + SizeChangedEventArgs args(this, this, old_size, size); + RaiseSizeChangedEvent(args); + if (auto window = GetWindow()) + window->Repaint(); + } + + Point Control::GetPositionAbsolute() const + { + return position_cache_.lefttop_position_absolute; + } + + Point Control::LocalToAbsolute(const Point& point) const + { + return Point(point.x + position_cache_.lefttop_position_absolute.x, + point.y + position_cache_.lefttop_position_absolute.y); + } + + Point Control::AbsoluteToLocal(const Point & point) const + { + return Point(point.x - position_cache_.lefttop_position_absolute.x, + point.y - position_cache_.lefttop_position_absolute.y); + } + + bool Control::IsPointInside(const Point & point) + { + const auto size = GetSize(); + return point.x >= 0.0f && point.x < size.width && point.y >= 0.0f && point.y < size.height; + } + + void Control::Draw(ID2D1DeviceContext* device_context) + { + D2D1::Matrix3x2F old_transform; + device_context->GetTransform(&old_transform); + + const auto position = GetPositionRelative(); + device_context->SetTransform(old_transform * D2D1::Matrix3x2F::Translation(position.x, position.y)); + + OnDraw(device_context); + DrawEventArgs args(this, this, device_context); + draw_event.Raise(args); + + for (auto child : GetChildren()) + child->Draw(device_context); + + device_context->SetTransform(old_transform); + } + + void Control::Repaint() + { + if (window_ != nullptr) + window_->Repaint(); + } + + bool Control::RequestFocus() + { + auto window = GetWindow(); + if (window == nullptr) + return false; + + return window->RequestFocusFor(this); + } + + bool Control::HasFocus() + { + auto window = GetWindow(); + if (window == nullptr) + return false; + + return window->GetFocusControl() == this; + } + + void Control::Relayout() + { + OnMeasure(GetSize()); + OnLayout(Rect(GetPositionRelative(), GetSize())); + } + + void Control::Measure(const Size& available_size) + { + SetDesiredSize(OnMeasure(available_size)); + } + + void Control::Layout(const Rect& rect) + { + SetPositionRelative(rect.GetLeftTop()); + SetSize(rect.GetSize()); + OnLayout(rect); + } + + Size Control::GetDesiredSize() const + { + return desired_size_; + } + + void Control::SetDesiredSize(const Size& desired_size) + { + desired_size_ = desired_size; + } + + void Control::OnAddChild(Control* child) + { + if (auto window = GetWindow()) + { + child->TraverseDescendants([window](Control* control) { + control->OnAttachToWindow(window); + }); + window->RefreshControlList(); + } + Relayout(); + } + + void Control::OnRemoveChild(Control* child) + { + if (auto window = GetWindow()) + { + child->TraverseDescendants([window](Control* control) { + control->OnDetachToWindow(window); + }); + window->RefreshControlList(); + } + Relayout(); + } + + void Control::OnAttachToWindow(Window* window) + { + window_ = window; + } + + void Control::OnDetachToWindow(Window * window) + { + window_ = nullptr; + } + + void Control::OnDraw(ID2D1DeviceContext * device_context) + { +#ifdef CRU_DEBUG_DRAW_CONTROL_BORDER + if (GetWindow()->GetDebugDrawControlBorder()) + { + auto brush = Application::GetInstance()->GetDebugBorderBrush(); + const auto size = GetSize(); + device_context->DrawRectangle(D2D1::RectF(0, 0, size.width, size.height), brush.Get()); + } +#endif + } + + void Control::OnPositionChanged(PositionChangedEventArgs & args) + { + + } + + void Control::OnSizeChanged(SizeChangedEventArgs & args) + { + } + + void Control::OnPositionChangedCore(PositionChangedEventArgs & args) + { + + } + + void Control::OnSizeChangedCore(SizeChangedEventArgs & args) + { + + } + + void Control::RaisePositionChangedEvent(PositionChangedEventArgs& args) + { + OnPositionChangedCore(args); + OnPositionChanged(args); + position_changed_event.Raise(args); + } + + void Control::RaiseSizeChangedEvent(SizeChangedEventArgs& args) + { + OnSizeChangedCore(args); + OnSizeChanged(args); + size_changed_event.Raise(args); + } + + void Control::OnMouseEnter(MouseEventArgs & args) + { + } + + void Control::OnMouseLeave(MouseEventArgs & args) + { + } + + void Control::OnMouseMove(MouseEventArgs & args) + { + } + + void Control::OnMouseDown(MouseButtonEventArgs & args) + { + } + + void Control::OnMouseUp(MouseButtonEventArgs & args) + { + } + + void Control::OnMouseClick(MouseButtonEventArgs& args) + { + + } + + void Control::OnMouseEnterCore(MouseEventArgs & args) + { + is_mouse_inside_ = true; + } + + void Control::OnMouseLeaveCore(MouseEventArgs & args) + { + is_mouse_inside_ = false; + 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); + } + } + } + + void Control::OnMouseMoveCore(MouseEventArgs & args) + { + + } + + void Control::OnMouseDownCore(MouseButtonEventArgs & args) + { + if (is_focus_on_pressed_ && args.GetSender() == args.GetOriginalSender()) + RequestFocus(); + is_mouse_click_valid_map_[args.GetMouseButton()] = true; + OnMouseClickBegin(args.GetMouseButton()); + } + + void Control::OnMouseUpCore(MouseButtonEventArgs & args) + { + if (is_mouse_click_valid_map_[args.GetMouseButton()]) + { + is_mouse_click_valid_map_[args.GetMouseButton()] = false; + RaiseMouseClickEvent(args); + OnMouseClickEnd(args.GetMouseButton()); + } + } + + void Control::OnMouseClickCore(MouseButtonEventArgs& args) + { + + } + + void Control::RaiseMouseEnterEvent(MouseEventArgs& args) + { + OnMouseEnterCore(args); + OnMouseEnter(args); + mouse_enter_event.Raise(args); + } + + void Control::RaiseMouseLeaveEvent(MouseEventArgs& args) + { + OnMouseLeaveCore(args); + OnMouseLeave(args); + mouse_leave_event.Raise(args); + } + + void Control::RaiseMouseMoveEvent(MouseEventArgs& args) + { + OnMouseMoveCore(args); + OnMouseMove(args); + mouse_move_event.Raise(args); + } + + void Control::RaiseMouseDownEvent(MouseButtonEventArgs& args) + { + OnMouseDownCore(args); + OnMouseDown(args); + mouse_down_event.Raise(args); + } + + void Control::RaiseMouseUpEvent(MouseButtonEventArgs& args) + { + OnMouseUpCore(args); + OnMouseUp(args); + mouse_up_event.Raise(args); + } + + void Control::RaiseMouseClickEvent(MouseButtonEventArgs& args) + { + OnMouseClickCore(args); + OnMouseClick(args); + mouse_click_event.Raise(args); + } + + void Control::OnMouseClickBegin(MouseButton button) + { + + } + + void Control::OnMouseClickEnd(MouseButton button) + { + + } + + void Control::OnKeyDown(KeyEventArgs& args) + { + } + + void Control::OnKeyUp(KeyEventArgs& args) + { + } + + void Control::OnChar(CharEventArgs& args) + { + } + + void Control::OnKeyDownCore(KeyEventArgs& args) + { + } + + void Control::OnKeyUpCore(KeyEventArgs& args) + { + } + + void Control::OnCharCore(CharEventArgs& args) + { + } + + void Control::RaiseKeyDownEvent(KeyEventArgs& args) + { + OnKeyDownCore(args); + OnKeyDown(args); + key_down_event.Raise(args); + } + + void Control::RaiseKeyUpEvent(KeyEventArgs& args) + { + OnKeyUpCore(args); + OnKeyUp(args); + key_up_event.Raise(args); + } + + void Control::RaiseCharEvent(CharEventArgs& args) + { + OnCharCore(args); + OnChar(args); + char_event.Raise(args); + } + + void Control::OnGetFocus(FocusChangeEventArgs& args) + { + + } + + void Control::OnLoseFocus(FocusChangeEventArgs& args) + { + + } + + void Control::OnGetFocusCore(FocusChangeEventArgs& args) + { + + } + + void Control::OnLoseFocusCore(FocusChangeEventArgs& args) + { + + } + + void Control::RaiseGetFocusEvent(FocusChangeEventArgs& args) + { + OnGetFocusCore(args); + OnGetFocus(args); + get_focus_event.Raise(args); + } + + void Control::RaiseLoseFocusEvent(FocusChangeEventArgs& args) + { + OnLoseFocusCore(args); + OnLoseFocus(args); + lose_focus_event.Raise(args); + } + + Size Control::OnMeasure(const Size& available_size) + { + const auto layout_params = GetLayoutParams(); + + 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(); + } + }; + + const 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)); + + 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) + ); + } + + void Control::OnLayout(const Rect& rect) + { + ForeachChild([rect](Control* control) + { + 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 + { + switch (alignment) + { + case Alignment::Center: + return (layout_length - control_length) / 2; + case Alignment::Start: + return 0; + case Alignment::End: + return layout_length - control_length; + default: + UnreachableCode(); + } + }; + + control->Layout(Rect(Point( + calculate_anchor(layout_params->width.alignment, rect.width, size.width), + calculate_anchor(layout_params->height.alignment, rect.height, size.height) + ), size)); + }); + } + + void Control::CheckAndNotifyPositionChanged() + { + if (this->old_position_ != this->position_) + { + PositionChangedEventArgs args(this, this, this->old_position_, this->position_); + this->RaisePositionChangedEvent(args); + this->old_position_ = this->position_; + } + } + + 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) + return nullptr; + + auto&& left_list = GetAncestorList(left); + auto&& right_list = GetAncestorList(right); + + // the root is different + if (left_list.front() != right_list.front()) + return nullptr; + + // find the last same control or the last control (one is ancestor of the other) + auto left_i = left_list.cbegin(); + auto right_i = right_list.cbegin(); + while (true) + { + if (left_i == left_list.cend()) + return *(--left_i); + if (right_i == right_list.cend()) + return *(--right_i); + if (*left_i != *right_i) + return *(--left_i); + ++left_i; + ++right_i; + } + } + + Control * IsAncestorOrDescendant(Control * left, Control * right) + { + //Search up along the trunk from "left". Return if find "right". + auto control = left; + while (control != nullptr) + { + if (control == right) + return control; + control = control->GetParent(); + } + //Search up along the trunk from "right". Return if find "left". + control = right; + while (control != nullptr) + { + if (control == left) + return control; + control = control->GetParent(); + } + return nullptr; + } + } +} |