#pragma once // ReSharper disable once CppUnusedIncludeDirective #include "pre.hpp" #include "system_headers.hpp" #include #include #include #include "base.hpp" #include "ui_base.hpp" #include "layout_base.hpp" #include "events/ui_event.hpp" #include "border_property.hpp" #include "cursor.hpp" #include "any_map.hpp" namespace cru::ui { class Window; struct AdditionalMeasureInfo { bool horizontal_stretchable = true; bool vertical_stretchable = true; }; struct AdditionalLayoutInfo { Point total_offset = Point::Zero(); }; //the position cache struct ControlPositionCache { //The lefttop relative to the ancestor. Point lefttop_position_absolute = Point::Zero(); }; class Control : public Object { friend class Window; protected: struct GeometryInfo { Microsoft::WRL::ComPtr border_geometry = nullptr; Microsoft::WRL::ComPtr padding_content_geometry = nullptr; Microsoft::WRL::ComPtr content_geometry = nullptr; }; 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 *************** virtual StringView GetControlType() const = 0; virtual const std::vector& GetInternalChildren() const = 0; Control* GetParent() const { return parent_ == nullptr ? internal_parent_ : parent_; } Control* GetInternalParent() const { return internal_parent_; } //Get the window if attached, otherwise, return nullptr. Window* GetWindow() const { return window_; } void SetParent(Control* parent); void SetInternalParent(Control* internal_parent); void SetDescendantWindow(Window* window); //Traverse the tree rooted the control including itself. void TraverseDescendants(const std::function& predicate); //*************** region: position and size *************** //Get the lefttop relative to its parent. virtual Point GetOffset(); //Get the actual size. virtual Size GetSize(); // If offset changes, call RefreshDescendantPositionCache. virtual void SetRect(const Rect& rect); //Get lefttop relative to ancestor. This is only valid when //attached to window. Notice that the value is cached. //You can invalidate and recalculate it by calling "InvalidatePositionCache". Point GetPositionAbsolute() const; //Local point to absolute point. Point ControlToWindow(const Point& point) const; //Absolute point to local point. Point WindowToControl(const Point& point) const; void RefreshDescendantPositionCache(); private: static void RefreshControlPositionCacheInternal(Control* control, const Point& parent_lefttop_absolute); public: // Default implement in Control is test point in border geometry's // fill and stroke with width of border. virtual bool IsPointInside(const Point& point); // Get the top control among all descendants (including self) in local coordinate. virtual Control* HitTest(const Point& point); //*************** region: graphic *************** bool IsClipContent() const { return clip_content_; } void SetClipContent(bool clip); //Draw this control and its child controls. void Draw(ID2D1DeviceContext* device_context); virtual void InvalidateDraw(); Microsoft::WRL::ComPtr GetForegroundBrush() const { return foreground_brush_; } void SetForegroundBrush(Microsoft::WRL::ComPtr foreground_brush) { foreground_brush_ = std::move(foreground_brush); InvalidateDraw(); } Microsoft::WRL::ComPtr GetBackgroundBrush() const { return background_brush_; } void SetBackgroundBrush(Microsoft::WRL::ComPtr background_brush) { background_brush_ = std::move(background_brush); InvalidateDraw(); } //*************** region: focus *************** bool RequestFocus(); bool HasFocus(); bool IsFocusOnPressed() const { return is_focus_on_pressed_; } void SetFocusOnPressed(const bool value) { is_focus_on_pressed_ = value; } //*************** region: layout *************** void InvalidateLayout(); void Measure(const Size& available_size, const AdditionalMeasureInfo& additional_info); void Layout(const Rect& rect, const AdditionalLayoutInfo& additional_info); Size GetDesiredSize() const; void SetDesiredSize(const Size& desired_size); BasicLayoutParams* GetLayoutParams() { return &layout_params_; } Rect GetRect(RectRange range); Point TransformPoint(const Point& point, RectRange from = RectRange::Margin, RectRange to = RectRange::Content); //*************** region: border *************** BorderProperty& GetBorderProperty() { return border_property_; } void UpdateBorder(); bool IsBordered() const { return is_bordered_; } void SetBordered(bool bordered); //*************** region: additional properties *************** AnyMap* GetAdditionalPropertyMap() { return &additional_property_map_; } //*************** region: cursor *************** // If cursor is set to null, then it uses parent's cursor. // Window's cursor can't be null. Cursor::Ptr GetCursor() const { return cursor_; } void SetCursor(const Cursor::Ptr& cursor); //*************** region: events *************** //Raised when mouse enter the control. events::RoutedEvent mouse_enter_event; //Raised when mouse is leave the control. events::RoutedEvent mouse_leave_event; //Raised when mouse is move in the control. events::RoutedEvent mouse_move_event; //Raised when a mouse button is pressed in the control. events::RoutedEvent mouse_down_event; //Raised when a mouse button is released in the control. events::RoutedEvent mouse_up_event; //Raised when a mouse button is pressed in the control and released in the control with mouse not leaving it between two operations. events::RoutedEvent mouse_click_event; events::RoutedEvent mouse_wheel_event; events::RoutedEvent key_down_event; events::RoutedEvent key_up_event; events::RoutedEvent char_event; events::RoutedEvent get_focus_event; events::RoutedEvent lose_focus_event; Event draw_content_event; Event draw_background_event; Event draw_foreground_event; //*************** region: tree event *************** protected: virtual void OnParentChanged(Control* old_parent, Control* new_parent); virtual void OnInternalParentChanged(Control* old_internal_parent, Control* new_internal_parent); //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); //*************** region: graphic event *************** private: void OnDrawDecoration(ID2D1DeviceContext* device_context); void OnDrawCore(ID2D1DeviceContext* device_context); //*************** region: position and size event *************** protected: virtual void OnRectChange(const Rect& old_rect, const Rect& new_rect); void RegenerateGeometryInfo(); const GeometryInfo& GetGeometryInfo() const { return geometry_info_; } //*************** region: mouse event *************** protected: virtual void OnMouseClickBegin(MouseButton button); virtual void OnMouseClickEnd(MouseButton button); //*************** region: layout *************** private: Size OnMeasureCore(const Size& available_size, const AdditionalMeasureInfo& additional_info); void OnLayoutCore(const Rect& rect, const AdditionalLayoutInfo& additional_info); protected: virtual Size OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo& additional_info) = 0; virtual void OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info) = 0; private: Window * window_ = nullptr; Control* parent_ = nullptr; // when parent and internal parent are the same, parent_ is nullptr. Control * internal_parent_ = nullptr; Rect rect_{}; ControlPositionCache position_cache_{}; std::unordered_map is_mouse_click_valid_map_ { { MouseButton::Left, true }, { MouseButton::Middle, true }, { MouseButton::Right, true } }; // used for clicking determination BasicLayoutParams layout_params_{}; Size desired_size_ = Size::Zero(); bool is_bordered_ = false; BorderProperty border_property_; GeometryInfo geometry_info_{}; bool clip_content_ = false; Microsoft::WRL::ComPtr foreground_brush_ = nullptr; Microsoft::WRL::ComPtr background_brush_ = nullptr; AnyMap additional_property_map_{}; bool is_focus_on_pressed_ = true; #ifdef CRU_DEBUG_LAYOUT Microsoft::WRL::ComPtr margin_geometry_; Microsoft::WRL::ComPtr padding_geometry_; #endif Cursor::Ptr cursor_{}; }; class NoChildControl : public Control { private: // used in GetInternalChildren. static const std::vector empty_control_vector; protected: NoChildControl() = default; public: NoChildControl(const NoChildControl& other) = delete; NoChildControl(NoChildControl&& other) = delete; NoChildControl& operator=(const NoChildControl& other) = delete; NoChildControl& operator=(NoChildControl&& other) = delete; ~NoChildControl() override = default; const std::vector& GetInternalChildren() const override final { return empty_control_vector; } protected: void OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info) override; }; class SingleChildControl : public Control { protected: SingleChildControl(); public: SingleChildControl(const SingleChildControl& other) = delete; SingleChildControl(SingleChildControl&& other) = delete; SingleChildControl& operator=(const SingleChildControl& other) = delete; SingleChildControl& operator=(SingleChildControl&& other) = delete; ~SingleChildControl() override; const std::vector& GetInternalChildren() const override final { return child_vector_; } Control* GetChild() const { return child_; } void SetChild(Control* child); protected: // Override should call base. virtual void OnChildChanged(Control* old_child, Control* new_child); Size OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo& additional_info) override; void OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info) override; private: std::vector child_vector_; Control*& child_; }; class MultiChildControl : public Control { protected: MultiChildControl() = default; public: MultiChildControl(const MultiChildControl& other) = delete; MultiChildControl(MultiChildControl&& other) = delete; MultiChildControl& operator=(const MultiChildControl& other) = delete; MultiChildControl& operator=(MultiChildControl&& other) = delete; ~MultiChildControl() override; const std::vector& GetInternalChildren() const override final { return children_; } const std::vector& GetChildren() const { return children_; } //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); protected: //Invoked when a child is added. Overrides should invoke base. virtual void OnAddChild(Control* child); //Invoked when a child is removed. Overrides should invoke base. virtual void OnRemoveChild(Control* child); private: std::vector children_; }; //*************** region: event dispatcher helper *************** // Dispatch the event. // // This will raise routed event of the control and its parent and parent's // parent ... (until "last_receiver" if it's not nullptr) with appropriate args. // // First tunnel from top to bottom possibly stopped by "handled" flag in EventArgs. // Second bubble from bottom to top possibly stopped by "handled" flag in EventArgs. // Last direct to each control. // // 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 void DispatchEvent(Control* const original_sender, events::RoutedEvent Control::* event_ptr, Control* const last_receiver, Args&&... args) { std::list receive_list; auto parent = original_sender; while (parent != last_receiver) { receive_list.push_back(parent); parent = parent->GetInternalParent(); } auto handled = false; //tunnel for (auto i = receive_list.crbegin(); i != receive_list.crend(); ++i) { EventArgs event_args(*i, original_sender, std::forward(args)...); (*i->*event_ptr).tunnel.Raise(event_args); if (event_args.IsHandled()) { handled = true; break; } } //bubble if (!handled) { for (auto i : receive_list) { EventArgs event_args(i, original_sender, std::forward(args)...); (i->*event_ptr).bubble.Raise(event_args); if (event_args.IsHandled()) break; } } //direct for (auto i : receive_list) { EventArgs event_args(i, original_sender, std::forward(args)...); (i->*event_ptr).direct.Raise(event_args); } } //*************** region: tree helper *************** // Find the lowest common ancestor. // Return nullptr if "left" and "right" are not in the same tree. Control* FindLowestCommonAncestor(Control* left, Control* right); //*************** region: create helper *************** template TControl* CreateWithLayout(const LayoutSideParams& width, const LayoutSideParams& height, Args&&... args) { static_assert(std::is_base_of_v, "TControl is not a control class."); TControl* control = TControl::Create(std::forward(args)...); control->GetLayoutParams()->width = width; control->GetLayoutParams()->height = height; return control; } template TControl* CreateWithLayout(const Thickness& padding, const Thickness& margin, Args&&... args) { static_assert(std::is_base_of_v, "TControl is not a control class."); TControl* control = TControl::Create(std::forward(args)...); control->GetLayoutParams()->padding = padding; control->GetLayoutParams()->margin = margin; return control; } template TControl* CreateWithLayout(const LayoutSideParams& width, const LayoutSideParams& height, const Thickness& padding, const Thickness& margin, Args&&... args) { static_assert(std::is_base_of_v, "TControl is not a control class."); TControl* control = TControl::Create(std::forward(args)...); control->GetLayoutParams()->width = width; control->GetLayoutParams()->height = height; control->GetLayoutParams()->padding = padding; control->GetLayoutParams()->margin = margin; return control; } using ControlList = std::initializer_list; }