diff options
Diffstat (limited to 'CruUI/ui')
-rw-r--r-- | CruUI/ui/control.cpp | 488 | ||||
-rw-r--r-- | CruUI/ui/control.h | 248 | ||||
-rw-r--r-- | CruUI/ui/events/ui_event.cpp | 19 | ||||
-rw-r--r-- | CruUI/ui/events/ui_event.h | 180 | ||||
-rw-r--r-- | CruUI/ui/layout_base.h | 96 | ||||
-rw-r--r-- | CruUI/ui/ui_base.cpp | 8 | ||||
-rw-r--r-- | CruUI/ui/ui_base.h | 105 | ||||
-rw-r--r-- | CruUI/ui/window.cpp | 545 | ||||
-rw-r--r-- | CruUI/ui/window.h | 257 |
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 + }; + } +} |