aboutsummaryrefslogtreecommitdiff
path: root/src/ui/control.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/control.cpp')
-rw-r--r--src/ui/control.cpp712
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;
+ }
+ }
+}