diff options
author | crupest <crupest@outlook.com> | 2018-11-28 19:18:00 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2018-11-28 19:18:00 +0800 |
commit | f78458173c1baf567cc96880571b380e95a1039a (patch) | |
tree | 5bf790a573cff30071628329e1457e3affa0c2bc | |
parent | ee22597122612cd75fe62f5d808cb51478373fad (diff) | |
download | cru-f78458173c1baf567cc96880571b380e95a1039a.tar.gz cru-f78458173c1baf567cc96880571b380e95a1039a.tar.bz2 cru-f78458173c1baf567cc96880571b380e95a1039a.zip |
Refactor event system.
-rw-r--r-- | src/cru_event.hpp | 6 | ||||
-rw-r--r-- | src/main.cpp | 15 | ||||
-rw-r--r-- | src/ui/control.cpp | 379 | ||||
-rw-r--r-- | src/ui/control.hpp | 179 | ||||
-rw-r--r-- | src/ui/controls/list_item.cpp | 65 | ||||
-rw-r--r-- | src/ui/controls/list_item.hpp | 7 | ||||
-rw-r--r-- | src/ui/controls/popup_menu.cpp | 4 | ||||
-rw-r--r-- | src/ui/controls/scroll_control.cpp | 245 | ||||
-rw-r--r-- | src/ui/controls/scroll_control.hpp | 8 | ||||
-rw-r--r-- | src/ui/controls/text_box.cpp | 220 | ||||
-rw-r--r-- | src/ui/controls/text_box.hpp | 8 | ||||
-rw-r--r-- | src/ui/controls/text_control.cpp | 208 | ||||
-rw-r--r-- | src/ui/controls/text_control.hpp | 13 | ||||
-rw-r--r-- | src/ui/controls/toggle_button.cpp | 74 | ||||
-rw-r--r-- | src/ui/controls/toggle_button.hpp | 13 | ||||
-rw-r--r-- | src/ui/events/ui_event.hpp | 45 | ||||
-rw-r--r-- | src/ui/window.cpp | 30 | ||||
-rw-r--r-- | src/ui/window.hpp | 33 |
18 files changed, 630 insertions, 922 deletions
diff --git a/src/cru_event.hpp b/src/cru_event.hpp index 69832a0e..63de7e76 100644 --- a/src/cru_event.hpp +++ b/src/cru_event.hpp @@ -5,7 +5,7 @@ #include <type_traits> #include <functional> -#include <unordered_map> +#include <map> #include "base.hpp" @@ -76,13 +76,15 @@ namespace cru { (handler.second)(args); } + + //TODO: Remove this! bool IsNoHandler() const { return handlers_.empty(); } private: - std::unordered_map<EventHandlerToken, EventHandler> handlers_; + std::map<EventHandlerToken, EventHandler> handlers_; EventHandlerToken current_token_ = 0; }; diff --git a/src/main.cpp b/src/main.cpp index 7b27d95e..6dad0679 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -104,7 +104,7 @@ int APIENTRY wWinMain( //test 2 const auto layout = CreateWithLayout<LinearLayout>(LayoutSideParams::Exactly(500), LayoutSideParams::Content()); - layout->mouse_click_event.AddHandler([layout](cru::ui::events::MouseButtonEventArgs& args) + layout->mouse_click_event.bubble.AddHandler([layout](cru::ui::events::MouseButtonEventArgs& args) { if (args.GetSender() == args.GetOriginalSender()) layout->AddChild(TextBlock::Create(L"Layout is clicked!")); @@ -133,7 +133,7 @@ int APIENTRY wWinMain( const auto button = Button::Create(); button->GetLayoutParams()->padding = Thickness(20, 5); button->AddChild(TextBlock::Create(L"Show popup window parenting this.")); - button->mouse_click_event.AddHandler([window, button](auto) + button->mouse_click_event.bubble.AddHandler([window, button](auto) { std::vector<cru::ui::controls::MenuItemInfo> items; items.emplace_back(L"Hello world!", []{}); @@ -150,7 +150,7 @@ int APIENTRY wWinMain( button->GetLayoutParams()->padding = Thickness(20, 5); button->AddChild(TextBlock::Create(L"Show popup window parenting null.")); button->SetBackgroundBrush(cru::graph::CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Gold))); - button->mouse_click_event.AddHandler([](auto) + button->mouse_click_event.bubble.AddHandler([](auto) { auto popup = Window::CreatePopup(nullptr); popup->SetWindowRect(Rect(100, 100, 300, 300)); @@ -163,7 +163,7 @@ int APIENTRY wWinMain( const auto button = Button::Create(); button->GetLayoutParams()->padding = Thickness(20, 5); button->AddChild(TextBlock::Create(L"Show popup window with caption.")); - button->mouse_click_event.AddHandler([](auto) + button->mouse_click_event.bubble.AddHandler([](auto) { auto popup = Window::CreatePopup(nullptr, true); popup->SetWindowRect(Rect(100, 100, 300, 300)); @@ -175,7 +175,7 @@ int APIENTRY wWinMain( { const auto text_block = CreateWithLayout<TextBlock>(LayoutSideParams::Exactly(200), LayoutSideParams::Exactly(80), L"Hello World!!!"); - text_block->mouse_click_event.AddHandler([layout](cru::ui::events::MouseButtonEventArgs& args) + text_block->mouse_click_event.bubble.AddHandler([layout](cru::ui::events::MouseButtonEventArgs& args) { layout->AddChild(TextBlock::Create(L"Hello world is clicked!")); }); @@ -187,6 +187,11 @@ int APIENTRY wWinMain( const auto text_box = TextBox::Create(); text_box->GetLayoutParams()->width.min = 50.0f; text_box->GetLayoutParams()->width.max = 100.0f; + text_box->char_event.tunnel.AddHandler([](cru::ui::events::CharEventArgs& args) + { + if (args.GetChar() == L'1') + args.SetHandled(); + }); layout->AddChild(text_box); } diff --git a/src/ui/control.cpp b/src/ui/control.cpp index 2a81427a..0fec1ef8 100644 --- a/src/ui/control.cpp +++ b/src/ui/control.cpp @@ -16,12 +16,52 @@ namespace cru::ui { - using namespace events; - Control::Control(const bool container) : is_container_(container) { + mouse_leave_event.bubble.AddHandler([this](events::MouseEventArgs& args) + { + if (args.GetOriginalSender() != this) + return; + for (auto& is_mouse_click_valid : is_mouse_click_valid_map_) + { + if (is_mouse_click_valid.second) + { + is_mouse_click_valid.second = false; + OnMouseClickEnd(is_mouse_click_valid.first); + } + } + }); + + mouse_down_event.bubble.AddHandler([this](events::MouseButtonEventArgs& args) + { + if (args.GetOriginalSender() != this) + return; + if (is_focus_on_pressed_ && args.GetSender() == args.GetOriginalSender()) + RequestFocus(); + const auto button = args.GetMouseButton(); + is_mouse_click_valid_map_[button] = true; + OnMouseClickBegin(button); + }); + + mouse_up_event.bubble.AddHandler([this](events::MouseButtonEventArgs& args) + { + if (args.GetOriginalSender() != this) + return; + + const auto button = args.GetMouseButton(); + if (is_mouse_click_valid_map_[button]) + { + is_mouse_click_valid_map_[button] = false; + OnMouseClickEnd(button); + const auto point = args.GetPoint(GetWindow()); + InvokeLater([this, button, point] + { + DispatchEvent(this, &Control::mouse_click_event, nullptr, point, button); + }); + } + }); } Control::Control(WindowConstructorTag, Window* window) : Control(true) @@ -163,12 +203,41 @@ namespace cru::ui return size_; } + namespace + { +#ifdef CRU_DEBUG_LAYOUT + Microsoft::WRL::ComPtr<ID2D1Geometry> CalculateSquareRingGeometry(const Rect& out, const Rect& in) + { + const auto d2d1_factory = graph::GraphManager::GetInstance()->GetD2D1Factory(); + Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> out_geometry; + ThrowIfFailed(d2d1_factory->CreateRectangleGeometry(Convert(out), &out_geometry)); + Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> in_geometry; + ThrowIfFailed(d2d1_factory->CreateRectangleGeometry(Convert(in), &in_geometry)); + Microsoft::WRL::ComPtr<ID2D1PathGeometry> result_geometry; + ThrowIfFailed(d2d1_factory->CreatePathGeometry(&result_geometry)); + Microsoft::WRL::ComPtr<ID2D1GeometrySink> sink; + ThrowIfFailed(result_geometry->Open(&sink)); + ThrowIfFailed(out_geometry->CombineWithGeometry(in_geometry.Get(), D2D1_COMBINE_MODE_EXCLUDE, D2D1::Matrix3x2F::Identity(), sink.Get())); + ThrowIfFailed(sink->Close()); + return result_geometry; + } +#endif + } + void Control::SetSize(const Size & size) { const auto old_size = size_; size_ = size; - SizeChangedEventArgs args(this, this, old_size, size); - RaiseSizeChangedEvent(args); + events::SizeChangedEventArgs args(this, this, old_size, size); + size_changed_event.Raise(args); + + RegenerateGeometryInfo(); + +#ifdef CRU_DEBUG_LAYOUT + margin_geometry_ = CalculateSquareRingGeometry(GetRect(RectRange::Margin), GetRect(RectRange::FullBorder)); + padding_geometry_ = CalculateSquareRingGeometry(GetRect(RectRange::Padding), GetRect(RectRange::Content)); +#endif + if (auto window = GetWindow()) window->InvalidateDraw(); } @@ -476,8 +545,7 @@ namespace cru::ui graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(padding_rect.left, padding_rect.top), [this](ID2D1DeviceContext* device_context) { - OnDrawBackground(device_context); - DrawEventArgs args(this, this, device_context); + events::DrawEventArgs args(this, this, device_context); draw_background_event.Raise(args); }); @@ -486,8 +554,7 @@ namespace cru::ui graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(rect.left, rect.top), [this](ID2D1DeviceContext* device_context) { - OnDrawContent(device_context); - DrawEventArgs args(this, this, device_context); + events::DrawEventArgs args(this, this, device_context); draw_content_event.Raise(args); }); @@ -498,85 +565,11 @@ namespace cru::ui graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(padding_rect.left, padding_rect.top), [this](ID2D1DeviceContext* device_context) { - OnDrawForeground(device_context); - DrawEventArgs args(this, this, device_context); + events::DrawEventArgs args(this, this, device_context); draw_foreground_event.Raise(args); }); } - void Control::OnDrawContent(ID2D1DeviceContext * device_context) - { - - } - - void Control::OnDrawForeground(ID2D1DeviceContext* device_context) - { - - } - - void Control::OnDrawBackground(ID2D1DeviceContext* device_context) - { - - } - - void Control::OnPositionChanged(PositionChangedEventArgs & args) - { - - } - - void Control::OnSizeChanged(SizeChangedEventArgs & args) - { - } - - void Control::OnPositionChangedCore(PositionChangedEventArgs & args) - { - - } - - namespace - { -#ifdef CRU_DEBUG_LAYOUT - Microsoft::WRL::ComPtr<ID2D1Geometry> CalculateSquareRingGeometry(const Rect& out, const Rect& in) - { - const auto d2d1_factory = graph::GraphManager::GetInstance()->GetD2D1Factory(); - Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> out_geometry; - ThrowIfFailed(d2d1_factory->CreateRectangleGeometry(Convert(out), &out_geometry)); - Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> in_geometry; - ThrowIfFailed(d2d1_factory->CreateRectangleGeometry(Convert(in), &in_geometry)); - Microsoft::WRL::ComPtr<ID2D1PathGeometry> result_geometry; - ThrowIfFailed(d2d1_factory->CreatePathGeometry(&result_geometry)); - Microsoft::WRL::ComPtr<ID2D1GeometrySink> sink; - ThrowIfFailed(result_geometry->Open(&sink)); - ThrowIfFailed(out_geometry->CombineWithGeometry(in_geometry.Get(), D2D1_COMBINE_MODE_EXCLUDE, D2D1::Matrix3x2F::Identity(), sink.Get())); - ThrowIfFailed(sink->Close()); - return result_geometry; - } -#endif - } - - void Control::OnSizeChangedCore(SizeChangedEventArgs & args) - { - RegenerateGeometryInfo(); -#ifdef CRU_DEBUG_LAYOUT - margin_geometry_ = CalculateSquareRingGeometry(GetRect(RectRange::Margin), GetRect(RectRange::FullBorder)); - padding_geometry_ = CalculateSquareRingGeometry(GetRect(RectRange::Padding), GetRect(RectRange::Content)); -#endif - } - - void Control::RaisePositionChangedEvent(PositionChangedEventArgs& args) - { - OnPositionChangedCore(args); - OnPositionChanged(args); - position_changed_event.Raise(args); - } - - void Control::RaiseSizeChangedEvent(SizeChangedEventArgs& args) - { - OnSizeChangedCore(args); - OnSizeChanged(args); - size_changed_event.Raise(args); - } - void Control::RegenerateGeometryInfo() { if (IsBordered()) @@ -638,223 +631,12 @@ namespace cru::ui } } - void Control::OnMouseEnter(MouseEventArgs & args) - { - } - - void Control::OnMouseLeave(MouseEventArgs & args) - { - } - - void Control::OnMouseMove(MouseEventArgs & args) - { - } - - void Control::OnMouseDown(MouseButtonEventArgs & args) - { - } - - void Control::OnMouseUp(MouseButtonEventArgs & args) - { - } - - void Control::OnMouseClick(MouseButtonEventArgs& args) - { - - } - - void Control::OnMouseEnterCore(MouseEventArgs & args) - { - is_mouse_inside_ = true; - } - - void Control::OnMouseLeaveCore(MouseEventArgs & args) - { - is_mouse_inside_ = false; - for (auto& is_mouse_click_valid : is_mouse_click_valid_map_) - { - if (is_mouse_click_valid.second) - { - is_mouse_click_valid.second = false; - OnMouseClickEnd(is_mouse_click_valid.first); - } - } - } - - void Control::OnMouseMoveCore(MouseEventArgs & args) - { - - } - - void Control::OnMouseDownCore(MouseButtonEventArgs & args) - { - if (is_focus_on_pressed_ && args.GetSender() == args.GetOriginalSender()) - RequestFocus(); - is_mouse_click_valid_map_[args.GetMouseButton()] = true; - OnMouseClickBegin(args.GetMouseButton()); - } - - void Control::OnMouseUpCore(MouseButtonEventArgs & args) - { - if (is_mouse_click_valid_map_[args.GetMouseButton()]) - { - is_mouse_click_valid_map_[args.GetMouseButton()] = false; - RaiseMouseClickEvent(args); - OnMouseClickEnd(args.GetMouseButton()); - } - } - - void Control::OnMouseClickCore(MouseButtonEventArgs& args) - { - - } - - void Control::OnMouseWheel(events::MouseWheelEventArgs& args) - { - - } - - void Control::OnMouseWheelCore(events::MouseWheelEventArgs& args) - { - - } - - void Control::RaiseMouseEnterEvent(MouseEventArgs& args) - { - OnMouseEnterCore(args); - OnMouseEnter(args); - mouse_enter_event.Raise(args); - } - - void Control::RaiseMouseLeaveEvent(MouseEventArgs& args) - { - OnMouseLeaveCore(args); - OnMouseLeave(args); - mouse_leave_event.Raise(args); - } - - void Control::RaiseMouseMoveEvent(MouseEventArgs& args) - { - OnMouseMoveCore(args); - OnMouseMove(args); - mouse_move_event.Raise(args); - } - - void Control::RaiseMouseDownEvent(MouseButtonEventArgs& args) - { - OnMouseDownCore(args); - OnMouseDown(args); - mouse_down_event.Raise(args); - } - - void Control::RaiseMouseUpEvent(MouseButtonEventArgs& args) - { - OnMouseUpCore(args); - OnMouseUp(args); - mouse_up_event.Raise(args); - } - - void Control::RaiseMouseClickEvent(MouseButtonEventArgs& args) - { - OnMouseClickCore(args); - OnMouseClick(args); - mouse_click_event.Raise(args); - } - - void Control::RaiseMouseWheelEvent(MouseWheelEventArgs& args) - { - OnMouseWheelCore(args); - OnMouseWheel(args); - mouse_wheel_event.Raise(args); - } - void Control::OnMouseClickBegin(MouseButton button) { - } void Control::OnMouseClickEnd(MouseButton button) { - - } - - void Control::OnKeyDown(KeyEventArgs& args) - { - } - - void Control::OnKeyUp(KeyEventArgs& args) - { - } - - void Control::OnChar(CharEventArgs& args) - { - } - - void Control::OnKeyDownCore(KeyEventArgs& args) - { - } - - void Control::OnKeyUpCore(KeyEventArgs& args) - { - } - - void Control::OnCharCore(CharEventArgs& args) - { - } - - void Control::RaiseKeyDownEvent(KeyEventArgs& args) - { - OnKeyDownCore(args); - OnKeyDown(args); - key_down_event.Raise(args); - } - - void Control::RaiseKeyUpEvent(KeyEventArgs& args) - { - OnKeyUpCore(args); - OnKeyUp(args); - key_up_event.Raise(args); - } - - void Control::RaiseCharEvent(CharEventArgs& args) - { - OnCharCore(args); - OnChar(args); - char_event.Raise(args); - } - - void Control::OnGetFocus(FocusChangeEventArgs& args) - { - - } - - void Control::OnLoseFocus(FocusChangeEventArgs& args) - { - - } - - void Control::OnGetFocusCore(FocusChangeEventArgs& args) - { - - } - - void Control::OnLoseFocusCore(FocusChangeEventArgs& args) - { - - } - - void Control::RaiseGetFocusEvent(FocusChangeEventArgs& args) - { - OnGetFocusCore(args); - OnGetFocus(args); - get_focus_event.Raise(args); - } - - void Control::RaiseLoseFocusEvent(FocusChangeEventArgs& args) - { - OnLoseFocusCore(args); - OnLoseFocus(args); - lose_focus_event.Raise(args); } inline Size ThicknessToSize(const Thickness& thickness) @@ -862,11 +644,6 @@ namespace cru::ui return Size(thickness.left + thickness.right, thickness.top + thickness.bottom); } - inline float AtLeast0(const float value) - { - return value < 0 ? 0 : value; - } - Size Control::OnMeasureCore(const Size& available_size) { const auto layout_params = GetLayoutParams(); @@ -1072,8 +849,8 @@ namespace cru::ui { if (this->old_position_ != this->position_) { - PositionChangedEventArgs args(this, this, this->old_position_, this->position_); - this->RaisePositionChangedEvent(args); + events::PositionChangedEventArgs args(this, this, this->old_position_, this->position_); + position_changed_event.Raise(args); this->old_position_ = this->position_; } } diff --git a/src/ui/control.hpp b/src/ui/control.hpp index d6ad9f02..f3751ddf 100644 --- a/src/ui/control.hpp +++ b/src/ui/control.hpp @@ -249,34 +249,35 @@ namespace cru::ui //*************** region: events *************** //Raised when mouse enter the control. - events::MouseEvent mouse_enter_event; + events::RoutedEvent<events::MouseEventArgs> mouse_enter_event; //Raised when mouse is leave the control. - events::MouseEvent mouse_leave_event; + events::RoutedEvent<events::MouseEventArgs> mouse_leave_event; //Raised when mouse is move in the control. - events::MouseEvent mouse_move_event; + events::RoutedEvent<events::MouseEventArgs> mouse_move_event; //Raised when a mouse button is pressed in the control. - events::MouseButtonEvent mouse_down_event; + events::RoutedEvent<events::MouseButtonEventArgs> mouse_down_event; //Raised when a mouse button is released in the control. - events::MouseButtonEvent mouse_up_event; + events::RoutedEvent<events::MouseButtonEventArgs> 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::MouseButtonEvent mouse_click_event; + events::RoutedEvent<events::MouseButtonEventArgs> mouse_click_event; - events::MouseWheelEvent mouse_wheel_event; + events::RoutedEvent<events::MouseWheelEventArgs> mouse_wheel_event; - events::KeyEvent key_down_event; - events::KeyEvent key_up_event; - events::CharEvent char_event; + events::RoutedEvent<events::KeyEventArgs> key_down_event; + events::RoutedEvent<events::KeyEventArgs> key_up_event; + events::RoutedEvent<events::CharEventArgs> char_event; - events::FocusChangeEvent get_focus_event; - events::FocusChangeEvent lose_focus_event; + events::RoutedEvent<events::FocusChangeEventArgs> get_focus_event; + events::RoutedEvent<events::FocusChangeEventArgs> lose_focus_event; - events::DrawEvent draw_content_event; - events::DrawEvent draw_background_event; - events::DrawEvent draw_foreground_event; + Event<events::DrawEventArgs> draw_content_event; + Event<events::DrawEventArgs> draw_background_event; + Event<events::DrawEventArgs> draw_foreground_event; - events::PositionChangedEvent position_changed_event; - events::SizeChangedEvent size_changed_event; + Event<events::PositionChangedEventArgs> position_changed_event; + Event<events::SizeChangedEventArgs> size_changed_event; + //*************** region: tree event *************** protected: //Invoked when a child is added. Overrides should invoke base. virtual void OnAddChild(Control* child); @@ -288,32 +289,15 @@ namespace cru::ui //Invoked when the control is detached to a window. Overrides should invoke base. virtual void OnDetachToWindow(Window* window); - //*************** region: graphic events *************** + + //*************** region: graphic event *************** private: void OnDrawDecoration(ID2D1DeviceContext* device_context); void OnDrawCore(ID2D1DeviceContext* device_context); - protected: - virtual void OnDrawContent(ID2D1DeviceContext* device_context); - virtual void OnDrawForeground(ID2D1DeviceContext* device_context); - virtual void OnDrawBackground(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); - - void RaisePositionChangedEvent(events::PositionChangedEventArgs& args); - void RaiseSizeChangedEvent(events::SizeChangedEventArgs& args); - + protected: void RegenerateGeometryInfo(); const GeometryInfo& GetGeometryInfo() const @@ -321,63 +305,19 @@ namespace cru::ui return geometry_info_; } - //*************** 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 OnMouseClick(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); - virtual void OnMouseClickCore(events::MouseButtonEventArgs& args); - - virtual void OnMouseWheel(events::MouseWheelEventArgs& args); - virtual void OnMouseWheelCore(events::MouseWheelEventArgs& args); - - void RaiseMouseEnterEvent(events::MouseEventArgs& args); - void RaiseMouseLeaveEvent(events::MouseEventArgs& args); - void RaiseMouseMoveEvent(events::MouseEventArgs& args); - void RaiseMouseDownEvent(events::MouseButtonEventArgs& args); - void RaiseMouseUpEvent(events::MouseButtonEventArgs& args); - void RaiseMouseClickEvent(events::MouseButtonEventArgs& args); - - void RaiseMouseWheelEvent(events::MouseWheelEventArgs& args); + //*************** region: mouse event *************** + protected: virtual void OnMouseClickBegin(MouseButton button); virtual void OnMouseClickEnd(MouseButton button); - //*************** region: keyboard event *************** - virtual void OnKeyDown(events::KeyEventArgs& args); - virtual void OnKeyUp(events::KeyEventArgs& args); - virtual void OnChar(events::CharEventArgs& args); - - virtual void OnKeyDownCore(events::KeyEventArgs& args); - virtual void OnKeyUpCore(events::KeyEventArgs& args); - virtual void OnCharCore(events::CharEventArgs& args); - - void RaiseKeyDownEvent(events::KeyEventArgs& args); - void RaiseKeyUpEvent(events::KeyEventArgs& args); - void RaiseCharEvent(events::CharEventArgs& args); - - //*************** region: focus event *************** - virtual void OnGetFocus(events::FocusChangeEventArgs& args); - virtual void OnLoseFocus(events::FocusChangeEventArgs& args); - - virtual void OnGetFocusCore(events::FocusChangeEventArgs& args); - virtual void OnLoseFocusCore(events::FocusChangeEventArgs& args); - - void RaiseGetFocusEvent(events::FocusChangeEventArgs& args); - void RaiseLoseFocusEvent(events::FocusChangeEventArgs& args); //*************** region: layout *************** + private: Size OnMeasureCore(const Size& available_size); void OnLayoutCore(const Rect& rect); + protected: virtual Size OnMeasureContent(const Size& available_size); virtual void OnLayoutContent(const Rect& rect); @@ -416,8 +356,6 @@ namespace cru::ui ControlPositionCache position_cache_{}; - bool is_mouse_inside_ = false; - std::unordered_map<MouseButton, bool> is_mouse_click_valid_map_ { { MouseButton::Left, true }, @@ -450,6 +388,71 @@ namespace cru::ui Cursor::Ptr cursor_{}; }; + + //*************** 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<typename EventArgs, typename... Args> + void DispatchEvent(Control* const original_sender, events::RoutedEvent<EventArgs> Control::* event_ptr, Control* const last_receiver, Args&&... args) + { + std::list<Control*> receive_list; + + auto parent = original_sender; + while (parent != last_receiver) + { + receive_list.push_back(parent); + parent = parent->GetParent(); + } + + auto handled = false; + + //tunnel + for (auto i = receive_list.crbegin(); i != receive_list.crend(); ++i) + { + EventArgs event_args(*i, original_sender, std::forward<Args>(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>(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>(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); @@ -468,6 +471,8 @@ namespace cru::ui } + //*************** region: create helper *************** + template <typename TControl, typename... Args> TControl* CreateWithLayout(const Thickness& padding, const Thickness& margin, Args&&... args) { diff --git a/src/ui/controls/list_item.cpp b/src/ui/controls/list_item.cpp index bf61010d..92f8750f 100644 --- a/src/ui/controls/list_item.cpp +++ b/src/ui/controls/list_item.cpp @@ -15,6 +15,39 @@ namespace cru::ui::controls brushes_[State::Hover] .fill_brush = predefine_resources->list_item_hover_fill_brush; brushes_[State::Select].border_brush = predefine_resources->list_item_select_border_brush; brushes_[State::Select].fill_brush = predefine_resources->list_item_select_fill_brush; + + draw_foreground_event.AddHandler([this](events::DrawEventArgs& args) + { + const auto device_context = args.GetDeviceContext(); + const auto rect = Rect(Point::Zero(), GetRect(RectRange::Padding).GetSize()); + device_context->FillRectangle(Convert(rect), brushes_[state_].fill_brush.Get()); + device_context->DrawRectangle(Convert(rect.Shrink(Thickness(0.5))), brushes_[state_].border_brush.Get(), 1); + }); + + mouse_enter_event.bubble.AddHandler([this](events::MouseEventArgs& args) + { + if (GetState() == State::Select) + return; + + if (IsAnyMouseButtonDown()) + return; + + SetState(State::Hover); + }); + + mouse_leave_event.bubble.AddHandler([this](events::MouseEventArgs& args) + { + if (GetState() == State::Select) + return; + + SetState(State::Normal); + }); + + mouse_click_event.bubble.AddHandler([this](events::MouseButtonEventArgs& args) + { + if (args.GetMouseButton() == MouseButton::Left) + SetState(State::Select); + }); } StringView ListItem::GetControlType() const @@ -27,36 +60,4 @@ namespace cru::ui::controls state_ = state; InvalidateDraw(); } - - void ListItem::OnDrawForeground(ID2D1DeviceContext* device_context) - { - const auto rect = Rect(Point::Zero(), GetRect(RectRange::Padding).GetSize()); - device_context->FillRectangle(Convert(rect), brushes_[state_].fill_brush.Get()); - device_context->DrawRectangle(Convert(rect.Shrink(Thickness(0.5))), brushes_[state_].border_brush.Get(), 1); - } - - void ListItem::OnMouseEnterCore(events::MouseEventArgs& args) - { - if (GetState() == State::Select) - return; - - if (IsAnyMouseButtonDown()) - return; - - SetState(State::Hover); - } - - void ListItem::OnMouseLeaveCore(events::MouseEventArgs& args) - { - if (GetState() == State::Select) - return; - - SetState(State::Normal); - } - - void ListItem::OnMouseClickCore(events::MouseButtonEventArgs& args) - { - if (args.GetMouseButton() == MouseButton::Left) - SetState(State::Select); - } } diff --git a/src/ui/controls/list_item.hpp b/src/ui/controls/list_item.hpp index a77d13e6..e150efbb 100644 --- a/src/ui/controls/list_item.hpp +++ b/src/ui/controls/list_item.hpp @@ -56,13 +56,6 @@ namespace cru::ui::controls void SetState(State state); - protected: - void OnDrawForeground(ID2D1DeviceContext* device_context) override; - - void OnMouseEnterCore(events::MouseEventArgs& args) override final; - void OnMouseLeaveCore(events::MouseEventArgs& args) override final; - void OnMouseClickCore(events::MouseButtonEventArgs& args) override final; - private: State state_ = State::Normal; std::map<State, StateBrush> brushes_{}; diff --git a/src/ui/controls/popup_menu.cpp b/src/ui/controls/popup_menu.cpp index 88ea8f17..7f4b9d08 100644 --- a/src/ui/controls/popup_menu.cpp +++ b/src/ui/controls/popup_menu.cpp @@ -12,7 +12,7 @@ namespace cru::ui::controls { const auto popup = Window::CreatePopup(parent); - popup->lose_focus_event.AddHandler([popup](events::FocusChangeEventArgs& args) + popup->lose_focus_event.bubble.AddHandler([popup](events::FocusChangeEventArgs& args) { if (args.IsWindow()) popup->Close(); @@ -29,7 +29,7 @@ namespace cru::ui::controls ControlList{ text_block } ); - list_item->mouse_click_event.AddHandler([popup, action](events::MouseButtonEventArgs& args) + list_item->mouse_click_event.bubble.AddHandler([popup, action](events::MouseButtonEventArgs& args) { if (args.GetMouseButton() == MouseButton::Left) { diff --git a/src/ui/controls/scroll_control.cpp b/src/ui/controls/scroll_control.cpp index aa5403d4..07715892 100644 --- a/src/ui/controls/scroll_control.cpp +++ b/src/ui/controls/scroll_control.cpp @@ -17,6 +17,124 @@ namespace cru::ui::controls ScrollControl::ScrollControl(const bool container) : Control(container) { SetClipContent(true); + + draw_foreground_event.AddHandler([this](events::DrawEventArgs& args) + { + const auto device_context = args.GetDeviceContext(); + const auto predefined = UiManager::GetInstance()->GetPredefineResources(); + + if (is_horizontal_scroll_bar_visible_) + { + device_context->FillRectangle( + Convert(horizontal_bar_info_.border), + predefined->scroll_bar_background_brush.Get() + ); + + device_context->FillRectangle( + Convert(horizontal_bar_info_.bar), + predefined->scroll_bar_brush.Get() + ); + + device_context->DrawLine( + Convert(horizontal_bar_info_.border.GetLeftTop()), + Convert(horizontal_bar_info_.border.GetRightTop()), + predefined->scroll_bar_border_brush.Get() + ); + } + + if (is_vertical_scroll_bar_visible_) + { + device_context->FillRectangle( + Convert(vertical_bar_info_.border), + predefined->scroll_bar_background_brush.Get() + ); + + device_context->FillRectangle( + Convert(vertical_bar_info_.bar), + predefined->scroll_bar_brush.Get() + ); + + device_context->DrawLine( + Convert(vertical_bar_info_.border.GetLeftTop()), + Convert(vertical_bar_info_.border.GetLeftBottom()), + predefined->scroll_bar_border_brush.Get() + ); + } + }); + + mouse_down_event.bubble.AddHandler([this](events::MouseButtonEventArgs& args) + { + if (args.GetMouseButton() == MouseButton::Left) + { + const auto point = args.GetPoint(this); + if (is_vertical_scroll_bar_visible_ && vertical_bar_info_.bar.IsPointInside(point)) + { + GetWindow()->CaptureMouseFor(this); + is_pressing_scroll_bar_ = Orientation::Vertical; + pressing_delta_ = point.y - vertical_bar_info_.bar.top; + return; + } + + if (is_horizontal_scroll_bar_visible_ && horizontal_bar_info_.bar.IsPointInside(point)) + { + GetWindow()->CaptureMouseFor(this); + pressing_delta_ = point.x - horizontal_bar_info_.bar.left; + is_pressing_scroll_bar_ = Orientation::Horizontal; + return; + } + } + }); + + mouse_move_event.bubble.AddHandler([this](events::MouseEventArgs& args) + { + const auto mouse_point = args.GetPoint(this); + + if (is_pressing_scroll_bar_ == Orientation::Horizontal) + { + const auto new_head_position = mouse_point.x - pressing_delta_; + const auto new_offset = new_head_position / horizontal_bar_info_.border.width * view_width_; + SetScrollOffset(new_offset, std::nullopt); + return; + } + + if (is_pressing_scroll_bar_ == Orientation::Vertical) + { + const auto new_head_position = mouse_point.y - pressing_delta_; + const auto new_offset = new_head_position / vertical_bar_info_.border.height * view_height_; + SetScrollOffset(std::nullopt, new_offset); + return; + } + }); + + mouse_up_event.bubble.AddHandler([this](events::MouseButtonEventArgs& args) + { + if (args.GetMouseButton() == MouseButton::Left && is_pressing_scroll_bar_.has_value()) + { + GetWindow()->ReleaseCurrentMouseCapture(); + is_pressing_scroll_bar_ = std::nullopt; + } + }); + + mouse_wheel_event.bubble.AddHandler([this](events::MouseWheelEventArgs& args) + { + constexpr const auto view_delta = 30.0f; + + if (args.GetDelta() == 0.0f) + return; + + const auto content_rect = GetRect(RectRange::Content); + if (IsVerticalScrollEnabled() && GetScrollOffsetY() != (args.GetDelta() > 0.0f ? 0.0f : AtLeast0(GetViewHeight() - content_rect.height))) + { + SetScrollOffset(std::nullopt, GetScrollOffsetY() - args.GetDelta() / WHEEL_DELTA * view_delta); + return; + } + + if (IsHorizontalScrollEnabled() && GetScrollOffsetX() != (args.GetDelta() > 0.0f ? 0.0f : AtLeast0(GetViewWidth() - content_rect.width))) + { + SetScrollOffset(GetScrollOffsetX() - args.GetDelta() / WHEEL_DELTA * view_delta, std::nullopt); + return; + } + }); } ScrollControl::~ScrollControl() @@ -204,133 +322,6 @@ namespace cru::ui::controls UpdateScrollBarVisibility(); } - void ScrollControl::OnDrawForeground(ID2D1DeviceContext* device_context) - { - Control::OnDrawForeground(device_context); - - const auto predefined = UiManager::GetInstance()->GetPredefineResources(); - - if (is_horizontal_scroll_bar_visible_) - { - device_context->FillRectangle( - Convert(horizontal_bar_info_.border), - predefined->scroll_bar_background_brush.Get() - ); - - device_context->FillRectangle( - Convert(horizontal_bar_info_.bar), - predefined->scroll_bar_brush.Get() - ); - - device_context->DrawLine( - Convert(horizontal_bar_info_.border.GetLeftTop()), - Convert(horizontal_bar_info_.border.GetRightTop()), - predefined->scroll_bar_border_brush.Get() - ); - } - - if (is_vertical_scroll_bar_visible_) - { - device_context->FillRectangle( - Convert(vertical_bar_info_.border), - predefined->scroll_bar_background_brush.Get() - ); - - device_context->FillRectangle( - Convert(vertical_bar_info_.bar), - predefined->scroll_bar_brush.Get() - ); - - device_context->DrawLine( - Convert(vertical_bar_info_.border.GetLeftTop()), - Convert(vertical_bar_info_.border.GetLeftBottom()), - predefined->scroll_bar_border_brush.Get() - ); - } - } - - void ScrollControl::OnMouseDownCore(events::MouseButtonEventArgs& args) - { - Control::OnMouseDownCore(args); - - if (args.GetMouseButton() == MouseButton::Left) - { - const auto point = args.GetPoint(this); - if (is_vertical_scroll_bar_visible_ && vertical_bar_info_.bar.IsPointInside(point)) - { - GetWindow()->CaptureMouseFor(this); - is_pressing_scroll_bar_ = Orientation::Vertical; - pressing_delta_ = point.y - vertical_bar_info_.bar.top; - return; - } - - if (is_horizontal_scroll_bar_visible_ && horizontal_bar_info_.bar.IsPointInside(point)) - { - GetWindow()->CaptureMouseFor(this); - pressing_delta_ = point.x - horizontal_bar_info_.bar.left; - is_pressing_scroll_bar_ = Orientation::Horizontal; - return; - } - } - } - - void ScrollControl::OnMouseMoveCore(events::MouseEventArgs& args) - { - Control::OnMouseMoveCore(args); - - const auto mouse_point = args.GetPoint(this); - - if (is_pressing_scroll_bar_ == Orientation::Horizontal) - { - const auto new_head_position = mouse_point.x - pressing_delta_; - const auto new_offset = new_head_position / horizontal_bar_info_.border.width * view_width_; - SetScrollOffset(new_offset, std::nullopt); - return; - } - - if (is_pressing_scroll_bar_ == Orientation::Vertical) - { - const auto new_head_position = mouse_point.y - pressing_delta_; - const auto new_offset = new_head_position / vertical_bar_info_.border.height * view_height_; - SetScrollOffset(std::nullopt, new_offset); - return; - } - } - - void ScrollControl::OnMouseUpCore(events::MouseButtonEventArgs& args) - { - Control::OnMouseUpCore(args); - - if (args.GetMouseButton() == MouseButton::Left && is_pressing_scroll_bar_.has_value()) - { - GetWindow()->ReleaseCurrentMouseCapture(); - is_pressing_scroll_bar_ = std::nullopt; - } - } - - void ScrollControl::OnMouseWheelCore(events::MouseWheelEventArgs& args) - { - Control::OnMouseWheelCore(args); - - constexpr const auto view_delta = 30.0f; - - if (args.GetDelta() == 0.0f) - return; - - const auto content_rect = GetRect(RectRange::Content); - if (IsVerticalScrollEnabled() && GetScrollOffsetY() != (args.GetDelta() > 0.0f ? 0.0f : AtLeast0(GetViewHeight() - content_rect.height))) - { - SetScrollOffset(std::nullopt, GetScrollOffsetY() - args.GetDelta() / WHEEL_DELTA * view_delta); - return; - } - - if (IsHorizontalScrollEnabled() && GetScrollOffsetX() != (args.GetDelta() > 0.0f ? 0.0f : AtLeast0(GetViewWidth() - content_rect.width))) - { - SetScrollOffset(GetScrollOffsetX() - args.GetDelta() / WHEEL_DELTA * view_delta, std::nullopt); - return; - } - } - void ScrollControl::CoerceAndSetOffsets(const float offset_x, const float offset_y, const bool update_children) { const auto old_offset_x = offset_x_; diff --git a/src/ui/controls/scroll_control.hpp b/src/ui/controls/scroll_control.hpp index 0541a010..4eabc605 100644 --- a/src/ui/controls/scroll_control.hpp +++ b/src/ui/controls/scroll_control.hpp @@ -122,14 +122,6 @@ namespace cru::ui::controls void AfterLayoutSelf() override; - void OnDrawForeground(ID2D1DeviceContext* device_context) override; - - void OnMouseDownCore(events::MouseButtonEventArgs& args) override final; - void OnMouseMoveCore(events::MouseEventArgs& args) override final; - void OnMouseUpCore(events::MouseButtonEventArgs& args) override final; - - void OnMouseWheelCore(events::MouseWheelEventArgs& args) override; - private: void CoerceAndSetOffsets(float offset_x, float offset_y, bool update_children = true); void UpdateScrollBarVisibility(); diff --git a/src/ui/controls/text_box.cpp b/src/ui/controls/text_box.cpp index 83311548..28e1053d 100644 --- a/src/ui/controls/text_box.cpp +++ b/src/ui/controls/text_box.cpp @@ -20,149 +20,145 @@ namespace cru::ui::controls GetBorderProperty() = UiManager::GetInstance()->GetPredefineResources()->text_box_border; SetBordered(true); - } - - TextBox::~TextBox() = default; - StringView TextBox::GetControlType() const - { - return control_type; - } - - void TextBox::OnDrawContent(ID2D1DeviceContext* device_context) - { - TextControl::OnDrawContent(device_context); - if (is_caret_show_) + draw_content_event.AddHandler([this](events::DrawEventArgs& args) { - const auto caret_half_width = UiManager::GetInstance()->GetCaretInfo().half_caret_width; - FLOAT x, y; - DWRITE_HIT_TEST_METRICS metrics{}; - ThrowIfFailed(text_layout_->HitTestTextPosition(caret_position_, FALSE, &x, &y, &metrics)); - device_context->FillRectangle(D2D1::RectF(metrics.left - caret_half_width, metrics.top, metrics.left + caret_half_width, metrics.top + metrics.height), caret_brush_.Get()); - } - } + const auto device_context = args.GetDeviceContext(); + if (is_caret_show_) + { + const auto caret_half_width = UiManager::GetInstance()->GetCaretInfo().half_caret_width; + FLOAT x, y; + DWRITE_HIT_TEST_METRICS metrics{}; + ThrowIfFailed(text_layout_->HitTestTextPosition(caret_position_, FALSE, &x, &y, &metrics)); + device_context->FillRectangle(D2D1::RectF(metrics.left - caret_half_width, metrics.top, metrics.left + caret_half_width, metrics.top + metrics.height), caret_brush_.Get()); + } + }); - void TextBox::OnGetFocusCore(events::FocusChangeEventArgs& args) - { - TextControl::OnGetFocusCore(args); - assert(!caret_timer_.has_value()); - is_caret_show_ = true; - caret_timer_ = SetInterval(UiManager::GetInstance()->GetCaretInfo().caret_blink_duration, [this] + get_focus_event.bubble.AddHandler([this](events::FocusChangeEventArgs& args) { - is_caret_show_ = !is_caret_show_; - InvalidateDraw(); + assert(!caret_timer_.has_value()); + is_caret_show_ = true; + caret_timer_ = SetInterval(UiManager::GetInstance()->GetCaretInfo().caret_blink_duration, [this] + { + is_caret_show_ = !is_caret_show_; + InvalidateDraw(); + }); }); - } - void TextBox::OnLoseFocusCore(events::FocusChangeEventArgs& args) - { - Control::OnLoseFocusCore(args); - assert(caret_timer_.has_value()); - caret_timer_->Cancel(); - caret_timer_ = std::nullopt; - is_caret_show_ = false; - } + lose_focus_event.bubble.AddHandler([this](events::FocusChangeEventArgs& args) + { + assert(caret_timer_.has_value()); + caret_timer_->Cancel(); + caret_timer_ = std::nullopt; + is_caret_show_ = false; + }); - void TextBox::OnKeyDownCore(events::KeyEventArgs& args) - { - Control::OnKeyDownCore(args); - if (args.GetVirtualCode() == VK_LEFT && caret_position_ > 0) + key_down_event.bubble.AddHandler([this](events::KeyEventArgs& args) { - if (IsKeyDown(VK_SHIFT)) + if (args.GetVirtualCode() == VK_LEFT && caret_position_ > 0) { - if (GetCaretSelectionSide()) - ShiftLeftSelectionRange(-1); - else - ShiftRightSelectionRange(-1); - } - else - { - const auto selection = GetSelectedRange(); - if (selection.has_value()) + if (IsKeyDown(VK_SHIFT)) { - ClearSelection(); - caret_position_ = selection.value().position; + if (GetCaretSelectionSide()) + ShiftLeftSelectionRange(-1); + else + ShiftRightSelectionRange(-1); } else - caret_position_--; + { + const auto selection = GetSelectedRange(); + if (selection.has_value()) + { + ClearSelection(); + caret_position_ = selection.value().position; + } + else + caret_position_--; + } + InvalidateDraw(); } - InvalidateDraw(); - } - if (args.GetVirtualCode() == VK_RIGHT && caret_position_ < GetText().size()) - { - if (IsKeyDown(VK_SHIFT)) + if (args.GetVirtualCode() == VK_RIGHT && caret_position_ < GetText().size()) { - if (GetCaretSelectionSide()) - ShiftLeftSelectionRange(1); - else - ShiftRightSelectionRange(1); - } - else - { - const auto selection = GetSelectedRange(); - if (selection.has_value()) + if (IsKeyDown(VK_SHIFT)) { - ClearSelection(); - caret_position_ = selection.value().position + selection.value().count; + if (GetCaretSelectionSide()) + ShiftLeftSelectionRange(1); + else + ShiftRightSelectionRange(1); } else - caret_position_++; + { + const auto selection = GetSelectedRange(); + if (selection.has_value()) + { + ClearSelection(); + caret_position_ = selection.value().position + selection.value().count; + } + else + caret_position_++; + } } - } - } + }); - void TextBox::OnCharCore(events::CharEventArgs& args) - { - Control::OnCharCore(args); - if (args.GetChar() == L'\b') + char_event.bubble.AddHandler([this](events::CharEventArgs& args) { - if (GetSelectedRange().has_value()) - { - const auto selection_range = GetSelectedRange().value(); - auto text = GetText(); - text.erase(text.cbegin() + selection_range.position, text.cbegin() + selection_range.position + selection_range.count); - SetText(text); - caret_position_ = selection_range.position; - ClearSelection(); - } - else + if (args.GetChar() == L'\b') { - if (caret_position_ > 0) + if (GetSelectedRange().has_value()) { + const auto selection_range = GetSelectedRange().value(); auto text = GetText(); - if (!text.empty()) + text.erase(text.cbegin() + selection_range.position, text.cbegin() + selection_range.position + selection_range.count); + SetText(text); + caret_position_ = selection_range.position; + ClearSelection(); + } + else + { + if (caret_position_ > 0) { - const auto position = --caret_position_; - text.erase(text.cbegin() + position); - SetText(text); + auto text = GetText(); + if (!text.empty()) + { + const auto position = --caret_position_; + text.erase(text.cbegin() + position); + SetText(text); + } } } + return; } - return; - } - if (std::iswprint(args.GetChar())) - { - if (GetSelectedRange().has_value()) - { - const auto selection_range = GetSelectedRange().value(); - auto text = GetText(); - text.erase(selection_range.position, selection_range.count); - text.insert(text.cbegin() + selection_range.position, args.GetChar()); - SetText(text); - caret_position_ = selection_range.position + 1; - ClearSelection(); - } - else + if (std::iswprint(args.GetChar())) { - ClearSelection(); - const auto position = caret_position_++; - auto text = GetText(); - text.insert(text.cbegin() + position, { args.GetChar() }); - SetText(text); + if (GetSelectedRange().has_value()) + { + const auto selection_range = GetSelectedRange().value(); + auto text = GetText(); + text.erase(selection_range.position, selection_range.count); + text.insert(text.cbegin() + selection_range.position, args.GetChar()); + SetText(text); + caret_position_ = selection_range.position + 1; + ClearSelection(); + } + else + { + ClearSelection(); + const auto position = caret_position_++; + auto text = GetText(); + text.insert(text.cbegin() + position, { args.GetChar() }); + SetText(text); + } } - } + }); + } + + TextBox::~TextBox() = default; + + StringView TextBox::GetControlType() const + { + return control_type; } void TextBox::RequestChangeCaretPosition(const unsigned position) diff --git a/src/ui/controls/text_box.hpp b/src/ui/controls/text_box.hpp index 3a30ecb2..e5cd7545 100644 --- a/src/ui/controls/text_box.hpp +++ b/src/ui/controls/text_box.hpp @@ -30,14 +30,6 @@ namespace cru::ui::controls StringView GetControlType() const override final; protected: - void OnDrawContent(ID2D1DeviceContext* device_context) override; - - void OnGetFocusCore(events::FocusChangeEventArgs& args) override final; - void OnLoseFocusCore(events::FocusChangeEventArgs& args) override final; - - void OnKeyDownCore(events::KeyEventArgs& args) override final; - void OnCharCore(events::CharEventArgs& args) override final; - void RequestChangeCaretPosition(unsigned position) override final; private: diff --git a/src/ui/controls/text_control.cpp b/src/ui/controls/text_control.cpp index d7d6b810..71a167e2 100644 --- a/src/ui/controls/text_control.cpp +++ b/src/ui/controls/text_control.cpp @@ -9,6 +9,40 @@ namespace cru::ui::controls { + namespace + { + unsigned TextLayoutHitTest(IDWriteTextLayout* text_layout, const Point& point) + { + BOOL is_trailing, is_inside; + DWRITE_HIT_TEST_METRICS metrics{}; + text_layout->HitTestPoint(point.x, point.y, &is_trailing, &is_inside, &metrics); + return is_trailing == 0 ? metrics.textPosition : metrics.textPosition + 1; + } + + void DrawSelectionRect(ID2D1DeviceContext* device_context, IDWriteTextLayout* layout, ID2D1Brush* brush, const std::optional<TextRange> range) + { + if (range.has_value()) + { + DWRITE_TEXT_METRICS text_metrics{}; + ThrowIfFailed(layout->GetMetrics(&text_metrics)); + const auto metrics_count = text_metrics.lineCount * text_metrics.maxBidiReorderingDepth; + + std::vector<DWRITE_HIT_TEST_METRICS> hit_test_metrics(metrics_count); + UINT32 actual_count; + layout->HitTestTextRange( + range.value().position, range.value().count, + 0, 0, + hit_test_metrics.data(), metrics_count, &actual_count + ); + + hit_test_metrics.erase(hit_test_metrics.cbegin() + actual_count, hit_test_metrics.cend()); + + for (const auto& metrics : hit_test_metrics) + device_context->FillRoundedRectangle(D2D1::RoundedRect(D2D1::RectF(metrics.left, metrics.top, metrics.left + metrics.width, metrics.top + metrics.height), 3, 3), brush); + } + } + } + TextControl::TextControl(const Microsoft::WRL::ComPtr<IDWriteTextFormat>& init_text_format, const Microsoft::WRL::ComPtr<ID2D1Brush>& init_brush) : Control(false) { @@ -21,6 +55,74 @@ namespace cru::ui::controls selection_brush_ = UiManager::GetInstance()->GetPredefineResources()->text_control_selection_brush; SetClipContent(true); + + size_changed_event.AddHandler([this](events::SizeChangedEventArgs& args) + { + const auto content = GetRect(RectRange::Content); + ThrowIfFailed(text_layout_->SetMaxWidth(content.width)); + ThrowIfFailed(text_layout_->SetMaxHeight(content.height)); + InvalidateDraw(); + }); + + draw_content_event.AddHandler([this](events::DrawEventArgs& args) + { + const auto device_context = args.GetDeviceContext(); + DrawSelectionRect(device_context, text_layout_.Get(), selection_brush_.Get(), selected_range_); + device_context->DrawTextLayout(D2D1::Point2F(), text_layout_.Get(), brush_.Get()); + }); + + mouse_down_event.bubble.AddHandler([this](events::MouseButtonEventArgs& args) + { + if (is_selectable_ && args.GetMouseButton() == MouseButton::Left && GetRect(RectRange::Padding).IsPointInside(args.GetPoint(this, RectRange::Margin))) + { + selected_range_ = std::nullopt; + const auto hit_test_result = TextLayoutHitTest(text_layout_.Get(), args.GetPoint(this)); + RequestChangeCaretPosition(hit_test_result); + mouse_down_position_ = hit_test_result; + is_selecting_ = true; + GetWindow()->CaptureMouseFor(this); + InvalidateDraw(); + } + }); + + mouse_move_event.bubble.AddHandler([this](events::MouseEventArgs& args) + { + if (is_selecting_) + { + const auto hit_test_result = TextLayoutHitTest(text_layout_.Get(), args.GetPoint(this)); + RequestChangeCaretPosition(hit_test_result); + selected_range_ = TextRange::FromTwoSides(hit_test_result, mouse_down_position_); + InvalidateDraw(); + } + UpdateCursor(args.GetPoint(this, RectRange::Margin)); + }); + + + mouse_up_event.bubble.AddHandler([this](events::MouseButtonEventArgs& args) + { + if (args.GetMouseButton() == MouseButton::Left) + { + if (is_selecting_) + { + is_selecting_ = false; + GetWindow()->ReleaseCurrentMouseCapture(); + } + } + }); + + lose_focus_event.bubble.AddHandler([this](events::FocusChangeEventArgs& args) + { + if (is_selecting_) + { + is_selecting_ = false; + GetWindow()->ReleaseCurrentMouseCapture(); + } + if (!args.IsWindow()) // If the focus lose is triggered window-wide, then save the selection state. Otherwise, clear selection. + { + selected_range_ = std::nullopt; + InvalidateDraw(); + } + }); } @@ -75,112 +177,6 @@ namespace cru::ui::controls } } - void TextControl::OnSizeChangedCore(events::SizeChangedEventArgs& args) - { - Control::OnSizeChangedCore(args); - const auto content = GetRect(RectRange::Content); - ThrowIfFailed(text_layout_->SetMaxWidth(content.width)); - ThrowIfFailed(text_layout_->SetMaxHeight(content.height)); - InvalidateDraw(); - } - - namespace - { - unsigned TextLayoutHitTest(IDWriteTextLayout* text_layout, const Point& point) - { - BOOL is_trailing, is_inside; - DWRITE_HIT_TEST_METRICS metrics{}; - text_layout->HitTestPoint(point.x, point.y, &is_trailing, &is_inside, &metrics); - return is_trailing == 0 ? metrics.textPosition : metrics.textPosition + 1; - } - - void DrawSelectionRect(ID2D1DeviceContext* device_context, IDWriteTextLayout* layout, ID2D1Brush* brush, const std::optional<TextRange> range) - { - if (range.has_value()) - { - DWRITE_TEXT_METRICS text_metrics{}; - ThrowIfFailed(layout->GetMetrics(&text_metrics)); - const auto metrics_count = text_metrics.lineCount * text_metrics.maxBidiReorderingDepth; - - std::vector<DWRITE_HIT_TEST_METRICS> hit_test_metrics(metrics_count); - UINT32 actual_count; - layout->HitTestTextRange( - range.value().position, range.value().count, - 0, 0, - hit_test_metrics.data(), metrics_count, &actual_count - ); - - hit_test_metrics.erase(hit_test_metrics.cbegin() + actual_count, hit_test_metrics.cend()); - - for (const auto& metrics : hit_test_metrics) - device_context->FillRoundedRectangle(D2D1::RoundedRect(D2D1::RectF(metrics.left, metrics.top, metrics.left + metrics.width, metrics.top + metrics.height), 3, 3), brush); - } - } - } - void TextControl::OnDrawContent(ID2D1DeviceContext* device_context) - { - Control::OnDrawContent(device_context); - DrawSelectionRect(device_context, text_layout_.Get(), selection_brush_.Get(), selected_range_); - device_context->DrawTextLayout(D2D1::Point2F(), text_layout_.Get(), brush_.Get()); - } - - void TextControl::OnMouseDownCore(events::MouseButtonEventArgs& args) - { - Control::OnMouseDownCore(args); - if (is_selectable_ && args.GetMouseButton() == MouseButton::Left && GetRect(RectRange::Padding).IsPointInside(args.GetPoint(this, RectRange::Margin))) - { - selected_range_ = std::nullopt; - const auto hit_test_result = TextLayoutHitTest(text_layout_.Get(), args.GetPoint(this)); - RequestChangeCaretPosition(hit_test_result); - mouse_down_position_ = hit_test_result; - is_selecting_ = true; - GetWindow()->CaptureMouseFor(this); - InvalidateDraw(); - } - } - - void TextControl::OnMouseMoveCore(events::MouseEventArgs& args) - { - Control::OnMouseMoveCore(args); - if (is_selecting_) - { - const auto hit_test_result = TextLayoutHitTest(text_layout_.Get(), args.GetPoint(this)); - RequestChangeCaretPosition(hit_test_result); - selected_range_ = TextRange::FromTwoSides(hit_test_result, mouse_down_position_); - InvalidateDraw(); - } - UpdateCursor(args.GetPoint(this, RectRange::Margin)); - } - - void TextControl::OnMouseUpCore(events::MouseButtonEventArgs& args) - { - Control::OnMouseUpCore(args); - if (args.GetMouseButton() == MouseButton::Left) - { - if (is_selecting_) - { - is_selecting_ = false; - GetWindow()->ReleaseCurrentMouseCapture(); - } - } - } - - void TextControl::OnLoseFocusCore(events::FocusChangeEventArgs& args) - { - Control::OnLoseFocusCore(args); - if (is_selecting_) - { - is_selecting_ = false; - GetWindow()->ReleaseCurrentMouseCapture(); - } - if (!args.IsWindow()) // If the focus lose is triggered window-wide, then save the selection state. Otherwise, clear selection. - { - selected_range_ = std::nullopt; - InvalidateDraw(); - } - } - - Size TextControl::OnMeasureContent(const Size& available_size) { ThrowIfFailed(text_layout_->SetMaxWidth(available_size.width)); diff --git a/src/ui/controls/text_control.hpp b/src/ui/controls/text_control.hpp index 762d85f3..1e4c985d 100644 --- a/src/ui/controls/text_control.hpp +++ b/src/ui/controls/text_control.hpp @@ -62,18 +62,7 @@ namespace cru::ui::controls protected: void SetSelectable(bool is_selectable); - protected: - void OnSizeChangedCore(events::SizeChangedEventArgs& args) override final; - void OnDrawContent(ID2D1DeviceContext* device_context) override; - - void OnMouseDownCore(events::MouseButtonEventArgs& args) override final; - void OnMouseMoveCore(events::MouseEventArgs& args) override final; - void OnMouseUpCore(events::MouseButtonEventArgs& args) override final; - - void OnLoseFocusCore(events::FocusChangeEventArgs& args) override; - - Size OnMeasureContent(const Size& available_size) override; - + Size OnMeasureContent(const Size& available_size) override final; virtual void RequestChangeCaretPosition(unsigned position); diff --git a/src/ui/controls/toggle_button.cpp b/src/ui/controls/toggle_button.cpp index e3d8662a..874a245f 100644 --- a/src/ui/controls/toggle_button.cpp +++ b/src/ui/controls/toggle_button.cpp @@ -3,6 +3,7 @@ #include "graph/graph.hpp" #include "ui/animations/animation.hpp" #include "ui/ui_manager.hpp" +#include "ui/convert_util.hpp" namespace cru::ui::controls { @@ -21,13 +22,34 @@ namespace cru::ui::controls on_brush_ = UiManager::GetInstance()->GetPredefineResources()->toggle_button_on_brush; off_brush_ = UiManager::GetInstance()->GetPredefineResources()->toggle_button_off_brush; - } - inline D2D1_POINT_2F ConvertPoint(const Point& point) - { - return D2D1::Point2F(point.x, point.y); + draw_content_event.AddHandler([this](events::DrawEventArgs& args) + { + const auto device_context = args.GetDeviceContext(); + const auto size = GetSize(); + graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(size.width / 2, size.height / 2), [this](ID2D1DeviceContext* device_context) + { + if (state_) + { + device_context->DrawGeometry(frame_path_.Get(), on_brush_.Get(), stroke_width); + device_context->FillEllipse(D2D1::Ellipse(D2D1::Point2F(current_circle_position_, 0), inner_circle_radius, inner_circle_radius), on_brush_.Get()); + } + else + { + device_context->DrawGeometry(frame_path_.Get(), off_brush_.Get(), stroke_width); + device_context->FillEllipse(D2D1::Ellipse(D2D1::Point2F(current_circle_position_, 0), inner_circle_radius, inner_circle_radius), off_brush_.Get()); + } + }); + }); + + mouse_click_event.bubble.AddHandler([this](events::MouseButtonEventArgs& args) + { + if (args.GetMouseButton() == MouseButton::Left) + Toggle(); + }); } + StringView ToggleButton::GetControlType() const { return control_type; @@ -38,9 +60,9 @@ namespace cru::ui::controls const auto size = GetSize(); const auto transform = D2D1::Matrix3x2F::Translation(size.width / 2, size.height / 2); BOOL contains; - frame_path_->FillContainsPoint(ConvertPoint(point), transform, &contains); + frame_path_->FillContainsPoint(Convert(point), transform, &contains); if (!contains) - frame_path_->StrokeContainsPoint(ConvertPoint(point), stroke_width, nullptr, transform, &contains); + frame_path_->StrokeContainsPoint(Convert(point), stroke_width, nullptr, transform, &contains); return contains != 0; } @@ -72,7 +94,8 @@ namespace cru::ui::controls }) .Start(); - RaiseToggleEvent(state); + events::ToggleEventArgs args(this, this, state); + toggle_event.Raise(args); InvalidateDraw(); } } @@ -82,36 +105,6 @@ namespace cru::ui::controls SetState(!GetState()); } - void ToggleButton::OnToggle(events::ToggleEventArgs& args) - { - - } - - void ToggleButton::OnDrawContent(ID2D1DeviceContext* device_context) - { - Control::OnDrawContent(device_context); - const auto size = GetSize(); - graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(size.width / 2, size.height / 2), [this](ID2D1DeviceContext* device_context) - { - if (state_) - { - device_context->DrawGeometry(frame_path_.Get(), on_brush_.Get(), stroke_width); - device_context->FillEllipse(D2D1::Ellipse(D2D1::Point2F(current_circle_position_, 0), inner_circle_radius, inner_circle_radius), on_brush_.Get()); - } - else - { - device_context->DrawGeometry(frame_path_.Get(), off_brush_.Get(), stroke_width); - device_context->FillEllipse(D2D1::Ellipse(D2D1::Point2F(current_circle_position_, 0), inner_circle_radius, inner_circle_radius), off_brush_.Get()); - } - }); - } - - void ToggleButton::OnMouseClickCore(events::MouseButtonEventArgs& args) - { - Control::OnMouseClickCore(args); - Toggle(); - } - Size ToggleButton::OnMeasureContent(const Size& available_size) { const Size result_size( @@ -121,11 +114,4 @@ namespace cru::ui::controls return result_size; } - - void ToggleButton::RaiseToggleEvent(bool new_state) - { - events::ToggleEventArgs args(this, this, new_state); - OnToggle(args); - toggle_event.Raise(args); - } } diff --git a/src/ui/controls/toggle_button.hpp b/src/ui/controls/toggle_button.hpp index 4cbb4f37..8b7402c8 100644 --- a/src/ui/controls/toggle_button.hpp +++ b/src/ui/controls/toggle_button.hpp @@ -40,23 +40,12 @@ namespace cru::ui::controls void Toggle(); - public: - events::ToggleEvent toggle_event; - - protected: - virtual void OnToggle(events::ToggleEventArgs& args); + Event<events::ToggleEventArgs> toggle_event; protected: - void OnDrawContent(ID2D1DeviceContext* device_context) override; - - void OnMouseClickCore(events::MouseButtonEventArgs& args) override; - Size OnMeasureContent(const Size& available_size) override; private: - void RaiseToggleEvent(bool new_state); - - private: bool state_ = false; float current_circle_position_; diff --git a/src/ui/events/ui_event.hpp b/src/ui/events/ui_event.hpp index 321e7135..8380827a 100644 --- a/src/ui/events/ui_event.hpp +++ b/src/ui/events/ui_event.hpp @@ -22,7 +22,7 @@ namespace cru::ui::events { public: UiEventArgs(Object* sender, Object* original_sender) - : BasicEventArgs(sender), original_sender_(original_sender) + : BasicEventArgs(sender), original_sender_(original_sender), handled_(false) { } @@ -38,10 +38,40 @@ namespace cru::ui::events return original_sender_; } + bool IsHandled() const + { + return handled_; + } + + void SetHandled(const bool handled = true) + { + handled_ = handled; + } + private: Object* original_sender_; + bool handled_; }; + template <typename TEventArgs> + class RoutedEvent + { + public: + static_assert(std::is_base_of_v<UiEventArgs, TEventArgs>, "TEventArgs must be subclass of UiEventArgs."); + + using EventArgs = TEventArgs; + + RoutedEvent() = default; + RoutedEvent(const RoutedEvent& other) = delete; + RoutedEvent(RoutedEvent&& other) = delete; + RoutedEvent& operator=(const RoutedEvent& other) = delete; + RoutedEvent& operator=(RoutedEvent&& other) = delete; + ~RoutedEvent() = default; + + Event<TEventArgs> direct; + Event<TEventArgs> bubble; + Event<TEventArgs> tunnel; + }; class MouseEventArgs : public UiEventArgs { @@ -327,17 +357,4 @@ namespace cru::ui::events private: wchar_t c_; }; - - using UiEvent = Event<UiEventArgs>; - using MouseEvent = Event<MouseEventArgs>; - using MouseButtonEvent = Event<MouseButtonEventArgs>; - using MouseWheelEvent = Event<MouseWheelEventArgs>; - using DrawEvent = Event<DrawEventArgs>; - using PositionChangedEvent = Event<PositionChangedEventArgs>; - using SizeChangedEvent = Event<SizeChangedEventArgs>; - using FocusChangeEvent = Event<FocusChangeEventArgs>; - using ToggleEvent = Event<ToggleEventArgs>; - using WindowNativeMessageEvent = Event<WindowNativeMessageEventArgs>; - using KeyEvent = Event<KeyEventArgs>; - using CharEvent = Event<CharEventArgs>; } diff --git a/src/ui/window.cpp b/src/ui/window.cpp index 9352b747..c757c4e1 100644 --- a/src/ui/window.cpp +++ b/src/ui/window.cpp @@ -533,11 +533,11 @@ namespace cru::ui if (focus_control_ == control) return true; - DispatchEvent(focus_control_, &Control::RaiseLoseFocusEvent, nullptr, false); + DispatchEvent(focus_control_, &Control::lose_focus_event, nullptr, false); focus_control_ = control; - DispatchEvent(control, &Control::RaiseGetFocusEvent, nullptr, false); + DispatchEvent(control, &Control::get_focus_event, nullptr, false); return true; } @@ -656,13 +656,13 @@ namespace cru::ui void Window::OnSetFocusInternal() { window_focus_ = true; - DispatchEvent(focus_control_, &Control::RaiseGetFocusEvent, nullptr, true); + DispatchEvent(focus_control_, &Control::get_focus_event, nullptr, true); } void Window::OnKillFocusInternal() { window_focus_ = false; - DispatchEvent(focus_control_, &Control::RaiseLoseFocusEvent, nullptr, true); + DispatchEvent(focus_control_, &Control::lose_focus_event, nullptr, true); } void Window::OnMouseMoveInternal(const POINT point) @@ -687,18 +687,18 @@ namespace cru::ui if (mouse_capture_control_) // if mouse is captured { - DispatchEvent(mouse_capture_control_, &Control::RaiseMouseMoveEvent, nullptr, dip_point); + DispatchEvent(mouse_capture_control_, &Control::mouse_move_event, nullptr, dip_point); } else { DispatchMouseHoverControlChangeEvent(old_control_mouse_hover, new_control_mouse_hover, dip_point); - DispatchEvent(new_control_mouse_hover, &Control::RaiseMouseMoveEvent, nullptr, dip_point); + DispatchEvent(new_control_mouse_hover, &Control::mouse_move_event, nullptr, dip_point); } } void Window::OnMouseLeaveInternal() { - DispatchEvent(mouse_hover_control_, &Control::RaiseMouseLeaveEvent, nullptr); + DispatchEvent(mouse_hover_control_, &Control::mouse_leave_event, nullptr); mouse_hover_control_ = nullptr; } @@ -713,7 +713,7 @@ namespace cru::ui else control = HitTest(dip_point); - DispatchEvent(control, &Control::RaiseMouseDownEvent, nullptr, dip_point, button); + DispatchEvent(control, &Control::mouse_down_event, nullptr, dip_point, button); } void Window::OnMouseUpInternal(MouseButton button, POINT point) @@ -727,7 +727,7 @@ namespace cru::ui else control = HitTest(dip_point); - DispatchEvent(control, &Control::RaiseMouseUpEvent, nullptr, dip_point, button); + DispatchEvent(control, &Control::mouse_up_event, nullptr, dip_point, button); } void Window::OnMouseWheelInternal(short delta, POINT point) @@ -741,22 +741,22 @@ namespace cru::ui else control = HitTest(dip_point); - DispatchEvent(control, &Control::RaiseMouseWheelEvent, nullptr, dip_point, static_cast<float>(delta)); + DispatchEvent(control, &Control::mouse_wheel_event, nullptr, dip_point, static_cast<float>(delta)); } void Window::OnKeyDownInternal(int virtual_code) { - DispatchEvent(focus_control_, &Control::RaiseKeyDownEvent, nullptr, virtual_code); + DispatchEvent(focus_control_, &Control::key_down_event, nullptr, virtual_code); } void Window::OnKeyUpInternal(int virtual_code) { - DispatchEvent(focus_control_, &Control::RaiseKeyUpEvent, nullptr, virtual_code); + DispatchEvent(focus_control_, &Control::key_up_event, nullptr, virtual_code); } void Window::OnCharInternal(wchar_t c) { - DispatchEvent(focus_control_, &Control::RaiseCharEvent, nullptr, c); + DispatchEvent(focus_control_, &Control::char_event, nullptr, c); } void Window::OnActivatedInternal() @@ -777,10 +777,10 @@ namespace cru::ui { const auto lowest_common_ancestor = FindLowestCommonAncestor(old_control, new_control); if (old_control != nullptr) // if last mouse-hover-on control exists - DispatchEvent(old_control, &Control::RaiseMouseLeaveEvent, lowest_common_ancestor); // dispatch mouse leave event. + DispatchEvent(old_control, &Control::mouse_leave_event, lowest_common_ancestor); // dispatch mouse leave event. if (new_control != nullptr) { - DispatchEvent(new_control, &Control::RaiseMouseEnterEvent, lowest_common_ancestor, point); // dispatch mouse enter event. + DispatchEvent(new_control, &Control::mouse_enter_event, lowest_common_ancestor, point); // dispatch mouse enter event. UpdateCursor(); } } diff --git a/src/ui/window.hpp b/src/ui/window.hpp index e82aa585..d7301eb5 100644 --- a/src/ui/window.hpp +++ b/src/ui/window.hpp @@ -4,6 +4,7 @@ #include "pre.hpp" #include "system_headers.hpp" +#include <list> #include <map> #include <memory> @@ -241,10 +242,10 @@ namespace cru::ui public: //*************** region: events *************** - events::UiEvent activated_event; - events::UiEvent deactivated_event; - - events::WindowNativeMessageEvent native_message_event; + Event<events::UiEventArgs> activated_event; + Event<events::UiEventArgs> deactivated_event; + + Event<events::WindowNativeMessageEventArgs> native_message_event; private: //*************** region: native operations *************** @@ -281,30 +282,6 @@ namespace cru::ui //*************** 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(); - } - } - void DispatchMouseHoverControlChangeEvent(Control* old_control, Control * new_control, const Point& point); private: |