aboutsummaryrefslogtreecommitdiff
path: root/CruUI/ui
diff options
context:
space:
mode:
Diffstat (limited to 'CruUI/ui')
-rw-r--r--CruUI/ui/control.cpp488
-rw-r--r--CruUI/ui/control.h248
-rw-r--r--CruUI/ui/events/ui_event.cpp19
-rw-r--r--CruUI/ui/events/ui_event.h180
-rw-r--r--CruUI/ui/layout_base.h96
-rw-r--r--CruUI/ui/ui_base.cpp8
-rw-r--r--CruUI/ui/ui_base.h105
-rw-r--r--CruUI/ui/window.cpp545
-rw-r--r--CruUI/ui/window.h257
9 files changed, 1946 insertions, 0 deletions
diff --git a/CruUI/ui/control.cpp b/CruUI/ui/control.cpp
new file mode 100644
index 00000000..32edc0cc
--- /dev/null
+++ b/CruUI/ui/control.cpp
@@ -0,0 +1,488 @@
+#include "control.h"
+
+#include <algorithm>
+
+#include "window.h"
+
+namespace cru {
+ namespace ui {
+ using namespace events;
+
+ Control::Control() :
+ window_(nullptr),
+ parent_(nullptr),
+ position_(Point::zero),
+ size_(Size::zero),
+ is_mouse_inside_(false),
+ layout_params_(nullptr),
+ desired_size_(Size::zero)
+ {
+
+ }
+
+ void Control::ForeachChild(Action<Control*>&& predicate)
+ {
+ for (const auto child : children_)
+ predicate(child);
+ }
+
+ void Control::ForeachChild(FlowControlAction<Control*>&& predicate)
+ {
+ for (const auto child : children_)
+ {
+ if (predicate(child) == FlowControl::Break)
+ break;
+ }
+ }
+
+ std::vector<Control*> Control::GetChildren()
+ {
+ return this->children_;
+ }
+
+ 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)
+ {
+ AddChildCheck(control);
+
+ this->children_.push_back(control);
+
+ control->parent_ = this;
+
+ this->OnAddChild(control);
+ }
+
+ void Control::AddChild(Control* control, int position)
+ {
+ 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)
+ {
+ 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(int position)
+ {
+ 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;
+ }
+
+ Window * Control::GetWindow()
+ {
+ return window_;
+ }
+
+ void TraverseDescendantsInternal(Control* control,
+ const std::function<void(Control*)>& predicate)
+ {
+ predicate(control);
+ control->ForeachChild([predicate](Control* c) {
+ TraverseDescendantsInternal(c, predicate);
+ });
+ }
+
+ void Control::TraverseDescendants(
+ const std::function<void(Control*)>& predicate)
+ {
+ TraverseDescendantsInternal(this, predicate);
+ }
+
+ Point Control::GetPositionRelative()
+ {
+ return position_;
+ }
+
+ void Control::SetPositionRelative(const Point & position)
+ {
+ position_ = position;
+ if (auto window = GetWindow())
+ {
+ window->GetLayoutManager()->InvalidateControlPositionCache(this);
+ window->Repaint();
+ }
+ //TODO: Position change notify.
+ }
+
+ 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);
+ OnSizeChangedCore(args);
+ if (auto window = GetWindow())
+ window->Repaint();
+ }
+
+ Point Control::GetPositionAbsolute()
+ {
+ return position_cache_.lefttop_position_absolute;
+ }
+
+ Point Control::LocalToAbsolute(const Point& point)
+ {
+ return Point(point.x + position_cache_.lefttop_position_absolute.x,
+ point.y + position_cache_.lefttop_position_absolute.y);
+ }
+
+ Point Control::AbsoluteToLocal(const Point & point)
+ {
+ return Point(point.x - position_cache_.lefttop_position_absolute.x,
+ point.y - position_cache_.lefttop_position_absolute.y);
+ }
+
+ bool Control::IsPointInside(const Point & point)
+ {
+ 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);
+
+ 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);
+ }
+
+ 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::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()
+ {
+ return desired_size_;
+ }
+
+ void Control::SetDesiredSize(const Size& desired_size)
+ {
+ desired_size_ = desired_size;
+ }
+
+ void Control::OnAddChild(Control* child)
+ {
+ if (auto window = dynamic_cast<Window*>(GetAncestor()))
+ {
+ child->TraverseDescendants([window](Control* control) {
+ control->OnAttachToWindow(window);
+ });
+ window->RefreshControlList();
+
+ }
+ }
+
+ void Control::OnRemoveChild(Control* child)
+ {
+ if (auto window = dynamic_cast<Window*>(GetAncestor()))
+ {
+ child->TraverseDescendants([window](Control* control) {
+ control->OnDetachToWindow(window);
+ });
+ window->RefreshControlList();
+ }
+ }
+
+ void Control::OnAttachToWindow(Window* window)
+ {
+ window_ = window;
+ }
+
+ void Control::OnDetachToWindow(Window * window)
+ {
+ window_ = nullptr;
+ }
+
+ void Control::OnDraw(ID2D1DeviceContext * device_context)
+ {
+
+ }
+
+ void Control::OnPositionChanged(PositionChangedEventArgs & args)
+ {
+
+ }
+
+ void Control::OnSizeChanged(SizeChangedEventArgs & args)
+ {
+ }
+
+ void Control::OnPositionChangedCore(PositionChangedEventArgs & args)
+ {
+ OnPositionChanged(args);
+ position_changed_event.Raise(args);
+ }
+
+ void Control::OnSizeChangedCore(SizeChangedEventArgs & 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::OnMouseEnterCore(MouseEventArgs & args)
+ {
+ is_mouse_inside_ = true;
+ OnMouseEnter(args);
+ mouse_enter_event.Raise(args);
+ }
+
+ void Control::OnMouseLeaveCore(MouseEventArgs & args)
+ {
+ is_mouse_inside_ = false;
+ OnMouseLeave(args);
+ mouse_leave_event.Raise(args);
+ }
+
+ void Control::OnMouseMoveCore(MouseEventArgs & args)
+ {
+ OnMouseMove(args);
+ mouse_move_event.Raise(args);
+ }
+
+ void Control::OnMouseDownCore(MouseButtonEventArgs & args)
+ {
+ OnMouseDown(args);
+ mouse_down_event.Raise(args);
+ }
+
+ void Control::OnMouseUpCore(MouseButtonEventArgs & args)
+ {
+ OnMouseUp(args);
+ mouse_up_event.Raise(args);
+ }
+
+ void Control::OnGetFocus(UiEventArgs & args)
+ {
+ }
+
+ void Control::OnLoseFocus(UiEventArgs & args)
+ {
+ }
+
+ void Control::OnGetFocusCore(UiEventArgs & args)
+ {
+ OnGetFocus(args);
+ get_focus_event.Raise(args);
+ }
+
+ void Control::OnLoseFocusCore(UiEventArgs & args)
+ {
+ OnLoseFocus(args);
+ lose_focus_event.Raise(args);
+ }
+
+ Size Control::OnMeasure(const Size& available_size)
+ {
+ const auto layout_params = GetLayoutParams();
+
+#ifdef _DEBUG
+ if (!layout_params->Validate())
+ ::OutputDebugStringW(L"LayoutParams is not valid.");
+#endif
+
+ auto&& f = [&](
+ const MeasureLength& layout_length,
+ const float available_length,
+ const std::optional<float> max_length,
+ const std::optional<float> min_length
+ ) -> float
+ {
+ float length;
+ switch (layout_length.mode)
+ {
+ case MeasureMode::Exactly:
+ {
+ length = std::maxlayout_length.length;
+ break;
+ }
+ case MeasureMode::Stretch:
+ case MeasureMode::Content:
+ return available_length;
+ default:
+ return 0.0f;
+ }
+ if (max_length.has_value())
+ length = std::min(max_length.value(), length);
+
+ };
+
+ Size size_for_children;
+ size_for_children.width = f(layout_params->size.width, available_size.width);
+ size_for_children.height = f(layout_params->size.height, available_size.height);
+
+
+ //TODO!
+ }
+
+ void Control::OnLayout(const Rect& rect)
+ {
+ //TODO!
+ }
+
+ 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 the other's ancestor)
+ 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;
+ }
+ }
+}
diff --git a/CruUI/ui/control.h b/CruUI/ui/control.h
new file mode 100644
index 00000000..e7fe2d45
--- /dev/null
+++ b/CruUI/ui/control.h
@@ -0,0 +1,248 @@
+#pragma once
+
+#include "system_headers.h"
+#include <vector>
+#include <optional>
+
+#include "base.h"
+#include "ui_base.h"
+#include "layout_base.h"
+#include "events/ui_event.h"
+
+namespace cru
+{
+ namespace ui
+ {
+ class Control;
+ class Window;
+
+
+ //the position cache
+ struct ControlPositionCache
+ {
+ //The lefttop relative to the ancestor.
+ Point lefttop_position_absolute;
+ };
+
+
+ class Control : public Object
+ {
+ friend class Window;
+ friend class WindowLayoutManager;
+ protected:
+ Control();
+
+ public:
+ Control(const Control& other) = delete;
+ Control(Control&& other) = delete;
+ Control& operator=(const Control& other) = delete;
+ Control& operator=(Control&& other) = delete;
+ ~Control() override = default;
+
+ public:
+
+ //*************** region: tree ***************
+
+ //Get parent of control, return nullptr if it has no parent.
+ Control* GetParent() const
+ {
+ return parent_;
+ }
+
+ //Traverse the children
+ void ForeachChild(Action<Control*>&& predicate);
+ void ForeachChild(FlowControlAction<Control*>&& predicate);
+
+ //Return a vector of all children. This function will create a
+ //temporary copy of vector of children. If you just want to
+ //traverse all children, just call ForeachChild.
+ std::vector<Control*> GetChildren();
+
+ //Add a child at tail.
+ void AddChild(Control* control);
+
+ //Add a child before the position.
+ void AddChild(Control* control, int position);
+
+ //Remove a child.
+ void RemoveChild(Control* child);
+
+ //Remove a child at specified position.
+ void RemoveChild(int position);
+
+ //Get the ancestor of the control.
+ Control* GetAncestor();
+
+ //Get the window if attached, otherwise, return nullptr.
+ Window* GetWindow();
+
+ //Traverse the tree rooted the control.
+ void TraverseDescendants(const std::function<void(Control*)>& predicate);
+
+ //*************** region: position and size ***************
+ // Position and size part must be isolated from layout part.
+ // All the operations in this part must be done independently.
+ // And layout part must use api of this part.
+
+ //Get the lefttop relative to its parent.
+ virtual Point GetPositionRelative();
+
+ //Set the lefttop relative to its parent.
+ virtual void SetPositionRelative(const Point& position);
+
+ //Get the actual size.
+ virtual Size GetSize();
+
+ //Set the actual size directly without relayout.
+ virtual void SetSize(const Size& size);
+
+ //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();
+
+ //Local point to absolute point.
+ Point LocalToAbsolute(const Point& point);
+
+ //Absolute point to local point.
+ Point AbsoluteToLocal(const Point& point);
+
+ bool IsPointInside(const Point& point);
+
+
+ //*************** region: graphic ***************
+
+ //Draw this control and its child controls.
+ void Draw(ID2D1DeviceContext* device_context);
+
+ //*************** region: focus ***************
+
+ bool RequestFocus();
+
+ bool HasFocus();
+
+
+ //*************** region: layout ***************
+
+ void Measure(const Size& available_size);
+
+ void Layout(const Rect& rect);
+
+ Size GetDesiredSize();
+
+ void SetDesiredSize(const Size& desired_size);
+
+ template<typename TLayoutParams = BasicLayoutParams>
+ std::shared_ptr<TLayoutParams> GetLayoutParams()
+ {
+ static_assert(std::is_base_of_v<BasicLayoutParams, TLayoutParams>, "TLayoutParams must be subclass of BasicLayoutParams.");
+ return static_cast<std::shared_ptr<BasicLayoutParams>>(layout_params_);
+ }
+
+ template<typename TLayoutParams = BasicLayoutParams,
+ typename = std::enable_if_t<std::is_base_of_v<BasicLayoutParams, TLayoutParams>>>
+ void SetLayoutParams(std::shared_ptr<TLayoutParams> basic_layout_params)
+ {
+ static_assert(std::is_base_of_v<BasicLayoutParams, TLayoutParams>, "TLayoutParams must be subclass of BasicLayoutParams.");
+ layout_params_ = basic_layout_params;
+ }
+
+ //*************** region: events ***************
+ //Raised when mouse enter the control.
+ events::MouseEvent mouse_enter_event;
+ //Raised when mouse is leave the control.
+ events::MouseEvent mouse_leave_event;
+ //Raised when mouse is move in the control.
+ events::MouseEvent mouse_move_event;
+ //Raised when a mouse button is pressed in the control.
+ events::MouseButtonEvent mouse_down_event;
+ //Raised when a mouse button is released in the control.
+ events::MouseButtonEvent mouse_up_event;
+
+ events::UiEvent get_focus_event;
+ events::UiEvent lose_focus_event;
+
+ events::DrawEvent draw_event;
+
+ events::PositionChangedEvent position_changed_event;
+ events::SizeChangedEvent size_changed_event;
+
+ 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);
+
+ //Invoked when the control is attached to a window. Overrides should invoke base.
+ virtual void OnAttachToWindow(Window* window);
+ //Invoked when the control is detached to a window. Overrides should invoke base.
+ virtual void OnDetachToWindow(Window* window);
+
+ virtual void OnDraw(ID2D1DeviceContext* device_context);
+
+
+ // For a event, the window event system will first dispatch event to core functions.
+ // Therefore for particular controls, you should do essential actions in core functions,
+ // and override version should invoke base version. The base core function
+ // in "Control" class will call corresponding non-core function and call "Raise" on
+ // event objects. So user custom actions should be done by overriding non-core function
+ // and calling the base version is optional.
+
+ //*************** region: position and size event ***************
+ virtual void OnPositionChanged(events::PositionChangedEventArgs& args);
+ virtual void OnSizeChanged(events::SizeChangedEventArgs& args);
+
+ virtual void OnPositionChangedCore(events::PositionChangedEventArgs& args);
+ virtual void OnSizeChangedCore(events::SizeChangedEventArgs& args);
+
+
+ //*************** region: mouse event ***************
+ virtual void OnMouseEnter(events::MouseEventArgs& args);
+ virtual void OnMouseLeave(events::MouseEventArgs& args);
+ virtual void OnMouseMove(events::MouseEventArgs& args);
+ virtual void OnMouseDown(events::MouseButtonEventArgs& args);
+ virtual void OnMouseUp(events::MouseButtonEventArgs& args);
+
+ virtual void OnMouseEnterCore(events::MouseEventArgs& args);
+ virtual void OnMouseLeaveCore(events::MouseEventArgs& args);
+ virtual void OnMouseMoveCore(events::MouseEventArgs& args);
+ virtual void OnMouseDownCore(events::MouseButtonEventArgs& args);
+ virtual void OnMouseUpCore(events::MouseButtonEventArgs& args);
+
+
+ //*************** region: focus event ***************
+ virtual void OnGetFocus(events::UiEventArgs& args);
+ virtual void OnLoseFocus(events::UiEventArgs& args);
+
+ virtual void OnGetFocusCore(events::UiEventArgs& args);
+ virtual void OnLoseFocusCore(events::UiEventArgs& args);
+
+ //*************** region: layout ***************
+ virtual Size OnMeasure(const Size& available_size);
+ virtual void OnLayout(const Rect& rect);
+
+ private:
+ Window * window_;
+
+ Control * parent_;
+ std::vector<Control*> children_;
+
+ Point position_;
+ Size size_;
+
+ ControlPositionCache position_cache_;
+
+ bool is_mouse_inside_;
+
+ std::shared_ptr<BasicLayoutParams> layout_params_;
+ Size desired_size_;
+ };
+
+ // Find the lowest common ancestor.
+ // Return nullptr if "left" and "right" are not in the same tree.
+ Control* FindLowestCommonAncestor(Control* left, Control* right);
+
+ // Return the ancestor if one control is the ancestor of the other one, otherwise nullptr.
+ Control* IsAncestorOrDescendant(Control* left, Control* right);
+ }
+}
diff --git a/CruUI/ui/events/ui_event.cpp b/CruUI/ui/events/ui_event.cpp
new file mode 100644
index 00000000..59623bab
--- /dev/null
+++ b/CruUI/ui/events/ui_event.cpp
@@ -0,0 +1,19 @@
+#include "ui_event.h"
+
+#include "ui/control.h"
+
+namespace cru
+{
+ namespace ui
+ {
+ namespace events
+ {
+ Point MouseEventArgs::GetPoint(Control* control) const
+ {
+ if (point_.has_value())
+ return control->AbsoluteToLocal(point_.value());
+ return Point();
+ }
+ }
+ }
+}
diff --git a/CruUI/ui/events/ui_event.h b/CruUI/ui/events/ui_event.h
new file mode 100644
index 00000000..efb0479b
--- /dev/null
+++ b/CruUI/ui/events/ui_event.h
@@ -0,0 +1,180 @@
+#pragma once
+
+#include "system_headers.h"
+#include <optional>
+
+#include "base.h"
+#include "cru_event.h"
+#include "ui/ui_base.h"
+
+namespace cru
+{
+ namespace ui
+ {
+ class Control;
+
+ namespace events
+ {
+ class UiEventArgs : public BasicEventArgs
+ {
+ public:
+ UiEventArgs(Object* sender, Object* original_sender)
+ : BasicEventArgs(sender), original_sender_(original_sender)
+ {
+
+ }
+
+ UiEventArgs(const UiEventArgs& other) = default;
+ UiEventArgs(UiEventArgs&& other) = default;
+ UiEventArgs& operator=(const UiEventArgs& other) = default;
+ UiEventArgs& operator=(UiEventArgs&& other) = default;
+ ~UiEventArgs() override = default;
+
+ Object* GetOriginalSender() const
+ {
+ return original_sender_;
+ }
+
+ private:
+ Object* original_sender_;
+ };
+
+
+ class MouseEventArgs : public UiEventArgs
+ {
+ public:
+ MouseEventArgs(Object* sender, Object* original_sender, const std::optional<Point>& point = std::nullopt)
+ : UiEventArgs(sender, original_sender), point_(point)
+ {
+
+ }
+ MouseEventArgs(const MouseEventArgs& other) = default;
+ MouseEventArgs(MouseEventArgs&& other) = default;
+ MouseEventArgs& operator=(const MouseEventArgs& other) = default;
+ MouseEventArgs& operator=(MouseEventArgs&& other) = default;
+ ~MouseEventArgs() override = default;
+
+ Point GetPoint(Control* control) const;
+
+ private:
+ std::optional<Point> point_;
+ };
+
+
+ class MouseButtonEventArgs : public MouseEventArgs
+ {
+ public:
+ MouseButtonEventArgs(Object* sender, Object* original_sender, const Point& point, const MouseButton button)
+ : MouseEventArgs(sender, original_sender, point), button_(button)
+ {
+
+ }
+ MouseButtonEventArgs(const MouseButtonEventArgs& other) = default;
+ MouseButtonEventArgs(MouseButtonEventArgs&& other) = default;
+ MouseButtonEventArgs& operator=(const MouseButtonEventArgs& other) = default;
+ MouseButtonEventArgs& operator=(MouseButtonEventArgs&& other) = default;
+ ~MouseButtonEventArgs() override = default;
+
+ MouseButton GetMouseButton() const
+ {
+ return button_;
+ }
+
+ private:
+ MouseButton button_;
+ };
+
+
+ class DrawEventArgs : public UiEventArgs
+ {
+ public:
+ DrawEventArgs(Object* sender, Object* original_sender, ID2D1DeviceContext* device_context)
+ : UiEventArgs(sender, original_sender), device_context_(device_context)
+ {
+
+ }
+ DrawEventArgs(const DrawEventArgs& other) = default;
+ DrawEventArgs(DrawEventArgs&& other) = default;
+ DrawEventArgs& operator=(const DrawEventArgs& other) = default;
+ DrawEventArgs& operator=(DrawEventArgs&& other) = default;
+ ~DrawEventArgs() = default;
+
+ ID2D1DeviceContext* GetDeviceContext() const
+ {
+ return device_context_;
+ }
+
+ private:
+ ID2D1DeviceContext * device_context_;
+ };
+
+
+ class PositionChangedEventArgs : public UiEventArgs
+ {
+ public:
+ PositionChangedEventArgs(Object* sender, Object* original_sender, const Point& old_position, const Point& new_position)
+ : UiEventArgs(sender, original_sender), old_position_(old_position), new_position_(new_position)
+ {
+
+ }
+ PositionChangedEventArgs(const PositionChangedEventArgs& other) = default;
+ PositionChangedEventArgs(PositionChangedEventArgs&& other) = default;
+ PositionChangedEventArgs& operator=(const PositionChangedEventArgs& other) = default;
+ PositionChangedEventArgs& operator=(PositionChangedEventArgs&& other) = default;
+ ~PositionChangedEventArgs() override = default;
+
+ Point GetOldPosition() const
+ {
+ return old_position_;
+ }
+
+ Point GetNewPosition() const
+ {
+ return new_position_;
+ }
+
+ private:
+ Point old_position_;
+ Point new_position_;
+ };
+
+
+ class SizeChangedEventArgs : public UiEventArgs
+ {
+ public:
+ SizeChangedEventArgs(Object* sender, Object* original_sender, const Size& old_size, const Size& new_size)
+ : UiEventArgs(sender, original_sender), old_size_(old_size), new_size_(new_size)
+ {
+
+ }
+ SizeChangedEventArgs(const SizeChangedEventArgs& other) = default;
+ SizeChangedEventArgs(SizeChangedEventArgs&& other) = default;
+ SizeChangedEventArgs& operator=(const SizeChangedEventArgs& other) = default;
+ SizeChangedEventArgs& operator=(SizeChangedEventArgs&& other) = default;
+ ~SizeChangedEventArgs() override = default;
+
+ Size GetOldSize() const
+ {
+ return old_size_;
+ }
+
+ Size GetNewSize() const
+ {
+ return new_size_;
+ }
+
+ private:
+ Size old_size_;
+ Size new_size_;
+ };
+
+
+ using UiEvent = Event<UiEventArgs>;
+ using MouseEvent = Event<MouseEventArgs>;
+ using MouseButtonEvent = Event<MouseButtonEventArgs>;
+ using DrawEvent = Event<DrawEventArgs>;
+ using PositionChangedEvent = Event<PositionChangedEventArgs>;
+ using SizeChangedEvent = Event<SizeChangedEventArgs>;
+ }
+ }
+}
diff --git a/CruUI/ui/layout_base.h b/CruUI/ui/layout_base.h
new file mode 100644
index 00000000..9bbbc9fd
--- /dev/null
+++ b/CruUI/ui/layout_base.h
@@ -0,0 +1,96 @@
+#pragma once
+
+#include <optional>
+
+namespace cru
+{
+ namespace ui
+ {
+ enum class MeasureMode
+ {
+ Exactly,
+ Content,
+ Stretch
+ };
+
+ struct MeasureLength final
+ {
+ explicit MeasureLength(const float length = 0.0, const MeasureMode mode = MeasureMode::Exactly)
+ : length(length), mode(mode)
+ {
+
+ }
+
+ bool Validate() const
+ {
+ return !(mode == MeasureMode::Exactly && length < 0.0);
+ }
+
+ float length;
+ MeasureMode mode;
+ };
+
+ struct MeasureSize final
+ {
+ MeasureLength width;
+ MeasureLength height;
+
+ bool Validate() const
+ {
+ return width.Validate() && height.Validate();
+ }
+ };
+
+ struct OptionalSize final
+ {
+ OptionalSize()
+ : width(std::nullopt), height(std::nullopt)
+ {
+
+ }
+
+ OptionalSize(const std::optional<float> width, const std::optional<float> height)
+ : width(width), height(height)
+ {
+
+ }
+
+ OptionalSize(const OptionalSize& other) = default;
+ OptionalSize(OptionalSize&& other) = default;
+ OptionalSize& operator = (const OptionalSize& other) = default;
+ OptionalSize& operator = (OptionalSize&& other) = default;
+ ~OptionalSize() = default;
+
+ bool Validate() const
+ {
+ if (width.has_value() && width.value() < 0.0)
+ return false;
+ if (height.has_value() && height.value() < 0.0)
+ return false;
+ return true;
+ }
+
+ std::optional<float> width;
+ std::optional<float> height;
+ };
+
+ struct BasicLayoutParams
+ {
+ BasicLayoutParams() = default;
+ BasicLayoutParams(const BasicLayoutParams&) = default;
+ BasicLayoutParams(BasicLayoutParams&&) = default;
+ BasicLayoutParams& operator = (const BasicLayoutParams&) = default;
+ BasicLayoutParams& operator = (BasicLayoutParams&&) = default;
+ virtual ~BasicLayoutParams() = default;
+
+ bool Validate() const
+ {
+ return size.Validate() && max_size.Validate() && min_size.Validate();
+ }
+
+ MeasureSize size;
+ OptionalSize min_size;
+ OptionalSize max_size;
+ };
+ }
+} \ No newline at end of file
diff --git a/CruUI/ui/ui_base.cpp b/CruUI/ui/ui_base.cpp
new file mode 100644
index 00000000..b30f65ab
--- /dev/null
+++ b/CruUI/ui/ui_base.cpp
@@ -0,0 +1,8 @@
+#include "ui_base.h"
+
+namespace cru {
+ namespace ui {
+ const Point Point::zero(0, 0);
+ const Size Size::zero(0, 0);
+ }
+}
diff --git a/CruUI/ui/ui_base.h b/CruUI/ui/ui_base.h
new file mode 100644
index 00000000..8dfbf53d
--- /dev/null
+++ b/CruUI/ui/ui_base.h
@@ -0,0 +1,105 @@
+#pragma once
+
+namespace cru
+{
+ namespace ui
+ {
+ struct Point
+ {
+ static const Point zero;
+
+ Point() = default;
+ Point(const float x, const float y) : x(x), y(y) { }
+
+ float x;
+ float y;
+ };
+
+ struct Size
+ {
+ static const Size zero;
+
+ Size() = default;
+ Size(const float width, const float height) : width(width), height(height) { }
+
+ float width;
+ float height;
+ };
+
+ struct Rect
+ {
+ Rect() = default;
+ Rect(const float left, const float top, const float width, const float height)
+ : left(left), top(top), width(width), height(height) { }
+ Rect(const Point& lefttop, const Size& size)
+ : left(lefttop.x), top(lefttop.y), width(size.width), height(size.height) { }
+
+ static Rect FromVertices(const float left, const float top, const float right, const float bottom)
+ {
+ return Rect(left, top, right - left, bottom - top);
+ }
+
+ float GetRight() const
+ {
+ return left + width;
+ }
+
+ float GetBottom() const
+ {
+ return top + height;
+ }
+
+ Point GetLeftTop() const
+ {
+ return Point(left, top);
+ }
+
+ Point GetRightBottom() const
+ {
+ return Point(left + width, top + height);
+ }
+
+ Size GetSize() const
+ {
+ return Size(width, height);
+ }
+
+ bool IsPointInside(const Point& point) const
+ {
+ return
+ point.x >= left &&
+ point.x < GetRight() &&
+ point.y >= top &&
+ point.y < GetBottom();
+ }
+
+ float left = 0.0f;
+ float top = 0.0f;
+ float width = 0.0f;
+ float height = 0.0f;
+ };
+
+ struct Thickness
+ {
+ Thickness() : Thickness(0) { }
+ explicit Thickness(const float width)
+ : left(width), top(width), right(width), bottom(width) { }
+
+ Thickness(const float left, const float top, const float right, const float bottom)
+ : left(left), top(top), right(right), bottom(bottom) { }
+
+
+ float left;
+ float top;
+ float right;
+ float bottom;
+ };
+
+ enum class MouseButton
+ {
+ Left,
+ Right,
+ Middle
+ };
+ }
+}
diff --git a/CruUI/ui/window.cpp b/CruUI/ui/window.cpp
new file mode 100644
index 00000000..22d97696
--- /dev/null
+++ b/CruUI/ui/window.cpp
@@ -0,0 +1,545 @@
+#include "window.h"
+
+#include "application.h"
+#include "graph/graph.h"
+#include "exception.h"
+
+namespace cru
+{
+ namespace ui
+ {
+ WindowClass::WindowClass(const std::wstring& name, WNDPROC window_proc, HINSTANCE hinstance)
+ : name_(name)
+ {
+ WNDCLASSEX window_class;
+ window_class.cbSize = sizeof(WNDCLASSEX);
+
+ window_class.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
+ window_class.lpfnWndProc = window_proc;
+ window_class.cbClsExtra = 0;
+ window_class.cbWndExtra = 0;
+ window_class.hInstance = hinstance;
+ 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.");
+ }
+
+ WindowClass::~WindowClass()
+ {
+
+ }
+
+ const wchar_t * WindowClass::GetName() {
+ return name_.c_str();
+ }
+
+ ATOM WindowClass::GetAtom() {
+ return atom_;
+ }
+
+ LRESULT __stdcall GeneralWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
+ auto window = Application::GetInstance()->GetWindowManager()->FromHandle(hWnd);
+
+ LRESULT result;
+ if (window != nullptr && window->HandleWindowMessage(hWnd, Msg, wParam, lParam, result))
+ return result;
+
+ return DefWindowProc(hWnd, Msg, wParam, lParam);
+ }
+
+ WindowManager::WindowManager() {
+ general_window_class_ = std::make_unique<WindowClass>(
+ L"CruUIWindowClass",
+ GeneralWndProc,
+ Application::GetInstance()->GetInstanceHandle()
+ );
+ }
+
+ void WindowManager::RegisterWindow(HWND hwnd, Window * window) {
+ const auto find_result = window_map_.find(hwnd);
+ if (find_result != window_map_.end())
+ throw std::runtime_error("The hwnd is already in the map.");
+
+ window_map_.emplace(hwnd, window);
+ }
+
+ void WindowManager::UnregisterWindow(HWND hwnd) {
+ const auto find_result = window_map_.find(hwnd);
+ if (find_result == window_map_.end())
+ throw std::runtime_error("The hwnd is not in the map.");
+ window_map_.erase(find_result);
+
+ if (window_map_.empty())
+ Application::GetInstance()->Quit(0);
+ }
+
+ Window* WindowManager::FromHandle(HWND hwnd) {
+ const auto find_result = window_map_.find(hwnd);
+ if (find_result == window_map_.end())
+ return nullptr;
+ else
+ return find_result->second;
+ }
+
+ WindowLayoutManager::WindowLayoutManager()
+ {
+ }
+
+ WindowLayoutManager::~WindowLayoutManager()
+ {
+ }
+
+ void WindowLayoutManager::InvalidateControlPositionCache(Control * control)
+ {
+ if (cache_invalid_controls_.count(control) == 1)
+ return;
+
+ // find descendant then erase it; find ancestor then just return.
+ for (auto i = cache_invalid_controls_.cbegin(); i != cache_invalid_controls_.cend(); ++i)
+ {
+ if (IsAncestorOrDescendant(*i, control) == control)
+ cache_invalid_controls_.erase(i);
+ else
+ return; // find a ancestor of "control", just return
+ }
+
+ cache_invalid_controls_.insert(control);
+
+ if (cache_invalid_controls_.size() == 1) // when insert just now and not repeat to "InvokeLater".
+ {
+ InvokeLater([this] {
+ RefreshInvalidControlPositionCache();
+ });
+ }
+ }
+
+ void WindowLayoutManager::RefreshInvalidControlPositionCache()
+ {
+ for (auto i : cache_invalid_controls_)
+ RefreshControlPositionCache(i);
+ cache_invalid_controls_.clear();
+ }
+
+ void WindowLayoutManager::RefreshControlPositionCache(Control * control)
+ {
+ Point point = Point::zero;
+ auto parent = control;
+ while ((parent = parent->GetParent())) {
+ const auto p = parent->GetPositionRelative();
+ point.x += p.x;
+ point.y += p.y;
+ }
+ RefreshControlPositionCacheInternal(control, point);
+ }
+
+ void WindowLayoutManager::RefreshControlPositionCacheInternal(Control * control, const Point & parent_lefttop_absolute)
+ {
+ const auto position = control->GetPositionRelative();
+ Point lefttop(
+ parent_lefttop_absolute.x + position.x,
+ parent_lefttop_absolute.y + position.x
+ );
+ control->position_cache_.lefttop_position_absolute = lefttop;
+ control->ForeachChild([lefttop](Control* c) {
+ RefreshControlPositionCacheInternal(c, lefttop);
+ });
+ }
+
+ Window::Window() : layout_manager_(new WindowLayoutManager()), control_list_({ this }) {
+ auto app = Application::GetInstance();
+ hwnd_ = CreateWindowEx(0,
+ app->GetWindowManager()->GetGeneralWindowClass()->GetName(),
+ L"", WS_OVERLAPPEDWINDOW,
+ CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+ nullptr, nullptr, app->GetInstanceHandle(), nullptr
+ );
+
+ if (hwnd_ == nullptr)
+ throw std::runtime_error("Failed to create window.");
+
+ app->GetWindowManager()->RegisterWindow(hwnd_, this);
+
+ render_target_ = app->GetGraphManager()->CreateWindowRenderTarget(hwnd_);
+ }
+
+ Window::~Window() {
+ Close();
+ }
+
+ WindowLayoutManager* Window::GetLayoutManager()
+ {
+ return layout_manager_.get();
+ }
+
+ HWND Window::GetWindowHandle()
+ {
+ return hwnd_;
+ }
+
+ bool Window::IsWindowValid() {
+ return hwnd_ != nullptr;
+ }
+
+ void Window::Close() {
+ if (IsWindowValid())
+ DestroyWindow(hwnd_);
+ }
+
+ void Window::Repaint() {
+ if (IsWindowValid()) {
+ InvalidateRect(hwnd_, nullptr, false);
+ UpdateWindow(hwnd_);
+ }
+ }
+
+ void Window::Show() {
+ if (IsWindowValid()) {
+ ShowWindow(hwnd_, SW_SHOWNORMAL);
+ }
+ }
+
+ void Window::Hide() {
+ if (IsWindowValid()) {
+ ShowWindow(hwnd_, SW_HIDE);
+ }
+ }
+
+ Size Window::GetClientSize() {
+ if (!IsWindowValid())
+ return Size();
+
+ const auto pixel_rect = GetClientRectPixel();
+ return Size(
+ graph::PixelToDipX(pixel_rect.right),
+ graph::PixelToDipY(pixel_rect.bottom)
+ );
+ }
+
+ void Window::SetClientSize(const Size & size) {
+ if (IsWindowValid()) {
+ const auto window_style = static_cast<DWORD>(GetWindowLongPtr(hwnd_, GWL_STYLE));
+ const auto window_ex_style = static_cast<DWORD>(GetWindowLongPtr(hwnd_, GWL_EXSTYLE));
+
+ RECT rect;
+ rect.left = 0;
+ rect.top = 0;
+ rect.right = graph::DipToPixelX(size.width);
+ rect.bottom = graph::DipToPixelY(size.height);
+ AdjustWindowRectEx(&rect, window_style, FALSE, window_ex_style);
+
+ SetWindowPos(
+ hwnd_, nullptr, 0, 0,
+ rect.right - rect.left,
+ rect.bottom - rect.top,
+ SWP_NOZORDER | SWP_NOMOVE
+ );
+ }
+ }
+
+ Rect Window::GetWindowRect() {
+ if (!IsWindowValid())
+ return Rect();
+
+ RECT rect;
+ ::GetWindowRect(hwnd_, &rect);
+
+ return Rect::FromVertices(
+ graph::PixelToDipX(rect.left),
+ graph::PixelToDipY(rect.top),
+ graph::PixelToDipX(rect.right),
+ graph::PixelToDipY(rect.bottom)
+ );
+ }
+
+ void Window::SetWindowRect(const Rect & rect) {
+ if (IsWindowValid()) {
+ SetWindowPos(
+ hwnd_, nullptr,
+ graph::DipToPixelX(rect.left),
+ graph::DipToPixelY(rect.top),
+ graph::DipToPixelX(rect.GetRight()),
+ graph::DipToPixelY(rect.GetBottom()),
+ SWP_NOZORDER
+ );
+ }
+ }
+
+ bool Window::HandleWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param, LRESULT & result) {
+ switch (msg) {
+ case WM_PAINT:
+ OnPaintInternal();
+ result = 0;
+ return true;
+ case WM_ERASEBKGND:
+ result = 1;
+ return true;
+ case WM_SETFOCUS:
+ OnSetFocusInternal();
+ result = 0;
+ return true;
+ case WM_KILLFOCUS:
+ OnKillFocusInternal();
+ result = 0;
+ return true;
+ case WM_LBUTTONDOWN:
+ {
+ POINT point;
+ point.x = GET_X_LPARAM(l_param);
+ point.y = GET_Y_LPARAM(l_param);
+ OnMouseDownInternal(MouseButton::Left, point);
+ result = 0;
+ return true;
+ }
+ case WM_LBUTTONUP:
+ {
+ POINT point;
+ point.x = GET_X_LPARAM(l_param);
+ point.y = GET_Y_LPARAM(l_param);
+ OnMouseDownInternal(MouseButton::Left, point);
+ result = 0;
+ return true;
+ }
+ case WM_RBUTTONDOWN:
+ {
+ POINT point;
+ point.x = GET_X_LPARAM(l_param);
+ point.y = GET_Y_LPARAM(l_param);
+ OnMouseDownInternal(MouseButton::Right, point);
+ result = 0;
+ return true;
+ }
+ case WM_RBUTTONUP:
+ {
+ POINT point;
+ point.x = GET_X_LPARAM(l_param);
+ point.y = GET_Y_LPARAM(l_param);
+ OnMouseDownInternal(MouseButton::Right, point);
+ result = 0;
+ return true;
+ }
+ case WM_MBUTTONDOWN:
+ {
+ POINT point;
+ point.x = GET_X_LPARAM(l_param);
+ point.y = GET_Y_LPARAM(l_param);
+ OnMouseDownInternal(MouseButton::Middle, point);
+ result = 0;
+ return true;
+ }
+ case WM_MBUTTONUP:
+ {
+ POINT point;
+ point.x = GET_X_LPARAM(l_param);
+ point.y = GET_Y_LPARAM(l_param);
+ OnMouseDownInternal(MouseButton::Middle, point);
+ result = 0;
+ return true;
+ }
+ case WM_SIZE:
+ OnResizeInternal(LOWORD(l_param), HIWORD(l_param));
+ result = 0;
+ return true;
+ case WM_DESTROY:
+ OnDestroyInternal();
+ result = 0;
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ Point Window::GetPositionRelative()
+ {
+ return Point();
+ }
+
+ void Window::SetPositionRelative(const Point & position)
+ {
+
+ }
+
+ Size Window::GetSize()
+ {
+ return GetClientSize();
+ }
+
+ void Window::SetSize(const Size & size)
+ {
+ SetClientSize(size);
+ }
+
+ void Window::RefreshControlList() {
+ control_list_.clear();
+ TraverseDescendants([this](Control* control) {
+ this->control_list_.push_back(control);
+ });
+ }
+
+ Control * Window::HitTest(const Point & point)
+ {
+ for (auto i = control_list_.crbegin(); i != control_list_.crend(); ++i) {
+ auto control = *i;
+ if (control->IsPointInside(control->AbsoluteToLocal(point))) {
+ return control;
+ }
+ }
+ return nullptr;
+ }
+
+ bool Window::RequestFocusFor(Control * control)
+ {
+ if (control == nullptr)
+ throw std::invalid_argument("The control to request focus can't be null. You can set it as the window.");
+
+ if (!IsWindowValid())
+ return false;
+
+ if (!window_focus_)
+ {
+ ::SetFocus(hwnd_);
+ focus_control_ = control;
+ return true; // event dispatch will be done in window message handling function "OnSetFocusInternal".
+ }
+
+ if (focus_control_ == control)
+ return true;
+
+ DispatchEvent(focus_control_, &Control::OnLoseFocusCore, nullptr);
+
+ focus_control_ = control;
+
+ DispatchEvent(control, &Control::OnGetFocusCore, nullptr);
+
+ return true;
+ }
+
+ Control* Window::GetFocusControl()
+ {
+ return focus_control_;
+ }
+
+ RECT Window::GetClientRectPixel() {
+ RECT rect{ };
+ GetClientRect(hwnd_, &rect);
+ return rect;
+ }
+
+ void Window::OnDestroyInternal() {
+ Application::GetInstance()->GetWindowManager()->UnregisterWindow(hwnd_);
+ hwnd_ = nullptr;
+ }
+
+ void Window::OnPaintInternal() {
+ render_target_->SetAsTarget();
+
+ auto device_context = render_target_->GetD2DDeviceContext();
+
+ device_context->BeginDraw();
+
+ //Clear the background.
+ device_context->Clear(D2D1::ColorF(D2D1::ColorF::White));
+
+ Draw(device_context.Get());
+
+ ThrowIfFailed(
+ device_context->EndDraw(), "Failed to draw window."
+ );
+
+ render_target_->Present();
+
+ ValidateRect(hwnd_, nullptr);
+ }
+
+ void Window::OnResizeInternal(int new_width, int new_height) {
+ render_target_->ResizeBuffer(new_width, new_height);
+ }
+
+ void Window::OnSetFocusInternal()
+ {
+ window_focus_ = true;
+ if (focus_control_ != nullptr)
+ DispatchEvent(focus_control_, &Control::OnGetFocusCore, nullptr);
+ }
+
+ void Window::OnKillFocusInternal()
+ {
+ window_focus_ = false;
+ if (focus_control_ != nullptr)
+ DispatchEvent(focus_control_, &Control::OnLoseFocusCore, nullptr);
+ }
+
+ void Window::OnMouseMoveInternal(POINT point)
+ {
+ Point dip_point(
+ graph::PixelToDipX(point.x),
+ graph::PixelToDipY(point.y)
+ );
+
+ //when mouse was previous outside the window
+ if (mouse_hover_control_ == nullptr) {
+ //invoke TrackMouseEvent to have WM_MOUSELEAVE sent.
+ TRACKMOUSEEVENT tme;
+ tme.cbSize = sizeof tme;
+ tme.dwFlags = TME_LEAVE;
+ tme.hwndTrack = hwnd_;
+
+ TrackMouseEvent(&tme);
+ }
+
+ //Find the first control that hit test succeed.
+ const auto new_control_mouse_hover = HitTest(dip_point);
+
+ if (new_control_mouse_hover != mouse_hover_control_) //if the mouse-hover-on control changed
+ {
+ const auto lowest_common_ancestor = FindLowestCommonAncestor(mouse_hover_control_, new_control_mouse_hover);
+ if (mouse_hover_control_ != nullptr) // if last mouse-hover-on control exists
+ {
+ // dispatch mouse leave event.
+ DispatchEvent(mouse_hover_control_, &Control::OnMouseLeaveCore, lowest_common_ancestor);
+ }
+ mouse_hover_control_ = new_control_mouse_hover;
+ // dispatch mouse enter event.
+ DispatchEvent(new_control_mouse_hover, &Control::OnMouseEnterCore, lowest_common_ancestor, dip_point);
+ }
+
+ DispatchEvent(new_control_mouse_hover, &Control::OnMouseMoveCore, nullptr, dip_point);
+ }
+
+ void Window::OnMouseLeaveInternal()
+ {
+ DispatchEvent(mouse_hover_control_, &Control::OnMouseLeaveCore, nullptr);
+ mouse_hover_control_ = nullptr;
+ }
+
+ void Window::OnMouseDownInternal(MouseButton button, POINT point)
+ {
+ Point dip_point(
+ graph::PixelToDipX(point.x),
+ graph::PixelToDipY(point.y)
+ );
+
+ const auto control = HitTest(dip_point);
+
+ DispatchEvent(control, &Control::OnMouseDownCore, nullptr, dip_point, button);
+ }
+
+ void Window::OnMouseUpInternal(MouseButton button, POINT point)
+ {
+ Point dip_point(
+ graph::PixelToDipX(point.x),
+ graph::PixelToDipY(point.y)
+ );
+
+ const auto control = HitTest(dip_point);
+
+ DispatchEvent(control, &Control::OnMouseUpCore, nullptr, dip_point, button);
+ }
+ }
+}
diff --git a/CruUI/ui/window.h b/CruUI/ui/window.h
new file mode 100644
index 00000000..1cb6b5f7
--- /dev/null
+++ b/CruUI/ui/window.h
@@ -0,0 +1,257 @@
+#pragma once
+
+#include "system_headers.h"
+#include <set>
+#include <map>
+#include <list>
+#include <memory>
+
+#include "Control.h"
+
+namespace cru {
+ namespace graph {
+ class WindowRenderTarget;
+ }
+
+ namespace ui {
+ class WindowClass : public Object
+ {
+ public:
+ WindowClass(const std::wstring& 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;
+
+
+ const wchar_t* GetName();
+ ATOM GetAtom();
+
+ private:
+ std::wstring name_;
+ ATOM atom_;
+ };
+
+ class WindowManager : public Object
+ {
+ public:
+ WindowManager();
+ WindowManager(const WindowManager& other) = delete;
+ WindowManager(WindowManager&& other) = delete;
+ WindowManager& operator=(const WindowManager& other) = delete;
+ WindowManager& operator=(WindowManager&& other) = delete;
+ ~WindowManager() override = default;
+
+
+ //Get the general window class for creating ordinary window.
+ WindowClass* GetGeneralWindowClass() const
+ {
+ return general_window_class_.get();
+ }
+
+ //Register a window newly created.
+ //This function adds the hwnd to hwnd-window map.
+ //It should be called immediately after a window was created.
+ void RegisterWindow(HWND hwnd, Window* window);
+
+ //Unregister a window that is going to be destroyed.
+ //This function removes the hwnd from the hwnd-window map.
+ //It should be called immediately before a window is going to be destroyed,
+ void UnregisterWindow(HWND hwnd);
+
+ //Return a pointer to the Window object related to the HWND or nullptr if the hwnd is not in the map.
+ Window* FromHandle(HWND hwnd);
+
+ private:
+ std::unique_ptr<WindowClass> general_window_class_;
+ std::map<HWND, Window*> window_map_;
+ };
+
+
+ class WindowLayoutManager : public Object
+ {
+ public:
+ WindowLayoutManager();
+ WindowLayoutManager(const WindowLayoutManager& other) = delete;
+ WindowLayoutManager(WindowLayoutManager&& other) = delete;
+ WindowLayoutManager& operator=(const WindowLayoutManager& other) = delete;
+ WindowLayoutManager& operator=(WindowLayoutManager&& other) = delete;
+ ~WindowLayoutManager() override;
+
+ //Mark position cache of the control and its descendants invalid,
+ //(which is saved as an auto-managed list internal)
+ //and send a message to refresh them.
+ void InvalidateControlPositionCache(Control* control);
+
+ //Refresh position cache of the control and its descendants whose cache
+ //has been marked as invalid.
+ void RefreshInvalidControlPositionCache();
+
+ //Refresh position cache of the control and its descendants immediately.
+ static void RefreshControlPositionCache(Control* control);
+
+ private:
+ static void RefreshControlPositionCacheInternal(Control* control, const Point& parent_lefttop_absolute);
+
+ private:
+ std::set<Control*> cache_invalid_controls_;
+ };
+
+ class Window : public Control
+ {
+ friend class WindowManager;
+ public:
+ Window();
+ Window(const Window& other) = delete;
+ Window(Window&& other) = delete;
+ Window& operator=(const Window& other) = delete;
+ Window& operator=(Window&& other) = delete;
+ ~Window() override;
+
+ public:
+ //*************** region: managers ***************
+ WindowLayoutManager* GetLayoutManager();
+
+
+ //*************** region: handle ***************
+
+ //Get the handle of the window. Return null if window is invalid.
+ HWND GetWindowHandle();
+
+ //Return if the window is still valid, that is, hasn't been closed or destroyed.
+ bool IsWindowValid();
+
+
+ //*************** region: window operations ***************
+
+ //Close and destroy the window if the window is valid.
+ void Close();
+
+ //Send a repaint message to the window's message queue which may make the window repaint.
+ void Repaint();
+
+ //Show the window.
+ void Show();
+
+ //Hide thw window.
+ void Hide();
+
+ //Get the client size.
+ Size GetClientSize();
+
+ //Set the client size and repaint.
+ void SetClientSize(const Size& size);
+
+ //Get the rect of the window containing frame.
+ //The lefttop of the rect is relative to screen lefttop.
+ Rect GetWindowRect();
+
+ //Set the rect of the window containing frame.
+ //The lefttop of the rect is relative to screen lefttop.
+ void SetWindowRect(const Rect& rect);
+
+ //Handle the raw window message.
+ //Return true if the message is handled and get the result through "result" argument.
+ //Return false if the message is not handled.
+ bool HandleWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param, LRESULT& result);
+
+
+ //*************** region: position and size ***************
+
+ //Always return (0, 0) for a window.
+ Point GetPositionRelative() override final;
+
+ //This method has no effect for a window.
+ void SetPositionRelative(const Point& position) override final;
+
+ //Get the size of client area for a window.
+ Size GetSize() override final;
+
+ //Set the size of client area for a window.
+ void SetSize(const Size& size) override final;
+
+
+ //*************** region: features ***************
+
+ //Refresh control list.
+ //It should be invoked every time a control is added or removed from the tree.
+ void RefreshControlList();
+
+ //Get the most top control at "point".
+ Control* HitTest(const Point& point);
+
+
+ //*************** region: focus ***************
+
+ //Request focus for specified control.
+ bool RequestFocusFor(Control* control);
+
+ //Get the control that has focus.
+ Control* GetFocusControl();
+
+
+ private:
+ //*************** region: native operations ***************
+
+ //Get the client rect in pixel.
+ RECT GetClientRectPixel();
+
+
+ //*************** region: native messages ***************
+
+ void OnDestroyInternal();
+ void OnPaintInternal();
+ void OnResizeInternal(int new_width, int new_height);
+
+ void OnSetFocusInternal();
+ void OnKillFocusInternal();
+
+ void OnMouseMoveInternal(POINT point);
+ void OnMouseLeaveInternal();
+ void OnMouseDownInternal(MouseButton button, POINT point);
+ void OnMouseUpInternal(MouseButton button, POINT point);
+
+
+
+ //*************** region: event dispatcher helper ***************
+
+ template<typename EventArgs>
+ using EventMethod = void (Control::*)(EventArgs&);
+
+ // Dispatch the event.
+ //
+ // This will invoke the "event_method" of the control and its parent and parent's
+ // parent ... (until "last_receiver" if it's not nullptr) with appropriate args.
+ //
+ // Args is of type "EventArgs". The first init argument is "sender", which is
+ // automatically bound to each receiving control. The second init argument is
+ // "original_sender", which is unchanged. And "args" will be perfectly forwarded
+ // as the rest arguments.
+ template<typename EventArgs, typename... Args>
+ void DispatchEvent(Control* original_sender, EventMethod<EventArgs> event_method, Control* last_receiver, Args&&... args)
+ {
+ auto control = original_sender;
+ while (control != nullptr && control != last_receiver)
+ {
+ EventArgs event_args(control, original_sender, std::forward<Args>(args)...);
+ (control->*event_method)(event_args);
+ control = control->GetParent();
+ }
+ }
+
+ private:
+ std::unique_ptr<WindowLayoutManager> layout_manager_;
+
+ HWND hwnd_ = nullptr;
+ std::shared_ptr<graph::WindowRenderTarget> render_target_{};
+
+ std::list<Control*> control_list_{};
+
+ Control* mouse_hover_control_ = nullptr;
+
+ bool window_focus_ = false;
+ Control* focus_control_ = this; // "focus_control_" can't be nullptr
+ };
+ }
+}