diff --git a/Engine/system/api/iosapi.mm b/Engine/system/api/iosapi.mm index 8539dbd0..59b36883 100644 --- a/Engine/system/api/iosapi.mm +++ b/Engine/system/api/iosapi.mm @@ -268,6 +268,11 @@ -(id)init { return self; } +- (void)setView:(UIView*)view { + view.multipleTouchEnabled = YES; + [super setView:view]; + } + - (void)viewDidLoad { [super viewDidLoad]; self.extendedLayoutIncludesOpaqueBars = YES; diff --git a/Engine/system/eventdispatcher.cpp b/Engine/system/eventdispatcher.cpp index f48c270d..66c10303 100644 --- a/Engine/system/eventdispatcher.cpp +++ b/Engine/system/eventdispatcher.cpp @@ -27,7 +27,7 @@ void EventDispatcher::dispatchMouseDown(Widget& wnd, MouseEvent &e) { Event::MouseDown ); e1.ignore(); - auto& btn = mouseUp[e.button]; + std::weak_ptr btn; for(auto i:overlays) { if(!i->bind(wnd)) continue; @@ -37,6 +37,7 @@ void EventDispatcher::dispatchMouseDown(Widget& wnd, MouseEvent &e) { if(e1.type()==MouseEvent::MouseDown) { mouseLast = btn; mouseLastBtn = e.button; + mouseLastId = e.mouseID; } } @@ -52,8 +53,11 @@ void EventDispatcher::dispatchMouseDown(Widget& wnd, MouseEvent &e) { mouseLast = btn; mouseLastTime = Application::tickCount(); mouseLastBtn = e.button; + mouseLastId = e.mouseID; } + reset(e.mouseID, e.button, btn); + if(auto w = btn.lock()) { if(w->widget->focusPolicy() & ClickFocus) { w->widget->implSetFocus(true,Event::FocusReason::ClickReason); @@ -64,9 +68,7 @@ void EventDispatcher::dispatchMouseDown(Widget& wnd, MouseEvent &e) { void EventDispatcher::dispatchMouseUp(Widget& /*wnd*/, MouseEvent &e) { ++mouseEvCount; - auto ptr = mouseUp[e.button]; - mouseUp[e.button].reset(); - + std::weak_ptr ptr = reset(e.mouseID, e.button); if(auto w = ptr.lock()) { auto p = e.pos() - w->widget->mapToRoot(Point()); MouseEvent e1( p.x, @@ -81,14 +83,15 @@ void EventDispatcher::dispatchMouseUp(Widget& /*wnd*/, MouseEvent &e) { } void EventDispatcher::dispatchMouseMove(Widget& wnd, MouseEvent &e) { - auto btn = Event::ButtonNone; - for(uint8_t i=0; iwidget->mapToRoot(Point()); MouseEvent e0( p.x, p.y, @@ -102,7 +105,7 @@ void EventDispatcher::dispatchMouseMove(Widget& wnd, MouseEvent &e) { return; } - if(auto w = lock(mouseUp[btn])) { + if(auto w = lock(e.mouseID,btn)) { auto p = e.pos() - w->widget->mapToRoot(Point()); MouseEvent e1( p.x, p.y, @@ -113,7 +116,7 @@ void EventDispatcher::dispatchMouseMove(Widget& wnd, MouseEvent &e) { Event::MouseMove ); w->widget->mouseMoveEvent(e1); if(e.isAccepted()) { - implSetMouseOver(mouseUp[btn].lock(),e); + implSetMouseOver(lock(e.mouseID,btn),e); return; } } @@ -304,7 +307,7 @@ std::shared_ptr EventDispatcher::implDispatch(Widget& w, MouseEvent auto last = mouseLast.lock(); bool dblClick = false; uint64_t time = Application::tickCount(); - if(time-mouseLastTime<1000 && mouseLastBtn==event.button && last!=nullptr && last->widget==it.owner) { + if(time-mouseLastTime<1000 && mouseLastBtn==event.button && mouseLastId==event.mouseID && last!=nullptr && last->widget==it.owner) { dblClick = true; } event.accept(); @@ -521,6 +524,29 @@ std::shared_ptr EventDispatcher::lock(std::weak_ptr& w return nullptr; } +std::shared_ptr EventDispatcher::lock(int id, Event::MouseButton button) { + for(auto& i:mouseUp) + if(i.id==id && i.button==button) + return lock(i.ref); + return nullptr; + } + +std::weak_ptr EventDispatcher::reset(int id, Event::MouseButton button, const std::weak_ptr& w) { + for(auto i=mouseUp.begin(); i!=mouseUp.end(); ++i) + if(i->id==id && i->button==button) { + auto ptr = i->ref; + if(w.expired()) { + mouseUp.erase(i); + } else { + i->ref = w; + } + return ptr.lock(); + } + if(!w.expired()) + mouseUp.push_back({button,id,w}); + return {}; + } + Event::Modifier EventDispatcher::mkModifier() const { uint8_t ret = 0; if(keyMod.ctrlL || keyMod.ctrlR) diff --git a/Engine/system/eventdispatcher.h b/Engine/system/eventdispatcher.h index fe9d0f8e..db2d916e 100644 --- a/Engine/system/eventdispatcher.h +++ b/Engine/system/eventdispatcher.h @@ -4,6 +4,7 @@ #include #include +#include namespace Tempest { @@ -33,6 +34,12 @@ class EventDispatcher final { void dispatchDestroyWindow(SystemApi::Window* w); private: + struct MouseCapture { + Event::MouseButton button = {}; + int id = 0; + std::weak_ptr ref = {}; + }; + std::shared_ptr implDispatch(Tempest::Widget &w, Tempest::MouseEvent& event); std::shared_ptr implDispatch(Tempest::Widget &w, Tempest::FocusEvent& event); void implMouseWheel(Widget &w, MouseEvent &event); @@ -44,9 +51,11 @@ class EventDispatcher final { void handleModKey(const KeyEvent& e); std::shared_ptr lock(std::weak_ptr& w); + std::shared_ptr lock(int id, Event::MouseButton button); + std::weak_ptr reset(int id, Event::MouseButton button, const std::weak_ptr& w = {}); Widget* customRoot = nullptr; - std::weak_ptr mouseUp[Event::MouseButton::ButtonLast]; + std::vector mouseUp; std::weak_ptr mouseLast; std::weak_ptr mouseOver; @@ -54,6 +63,7 @@ class EventDispatcher final { std::vector overlays; Event::MouseButton mouseLastBtn = Event::MouseButton::ButtonNone; + int mouseLastId = 0; uint64_t mouseLastTime = 0; uint64_t mouseEvCount = 0; diff --git a/Tests/tests/eventdispatcher_test.cpp b/Tests/tests/eventdispatcher_test.cpp index 5df4e00c..94ba35d7 100644 --- a/Tests/tests/eventdispatcher_test.cpp +++ b/Tests/tests/eventdispatcher_test.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include #include @@ -80,3 +82,246 @@ TEST(main,EventDispatcher_MouseEvent) { EXPECT_EQ(b0.up, 1); EXPECT_EQ(b0.move,1); } + +namespace { +struct PointerWidget : Widget { + std::vector events; + std::function onDown, onDrag, onUp; + int doubleClicks = 0; + + void mouseDownEvent(MouseEvent& e) override { + events.push_back(e); + if(onDown) + onDown(e); + } + void mouseDoubleClickEvent(MouseEvent& e) override { + ++doubleClicks; + mouseDownEvent(e); + } + void mouseDragEvent(MouseEvent& e) override { + events.push_back(e); + if(onDrag) + onDrag(e); + } + void mouseMoveEvent(MouseEvent& e) override { + events.push_back(e); + } + void mouseUpEvent(MouseEvent& e) override { + events.push_back(e); + if(onUp) + onUp(e); + } + }; + +struct PointerDispatch : Test { + Widget root; + EventDispatcher dispatcher{root}; + PointerWidget& left = root.addWidget(new PointerWidget()); + PointerWidget& right = root.addWidget(new PointerWidget()); + + PointerDispatch() { + root.resize(200,100); + left.setGeometry(0,0,100,100); + right.setGeometry(100,0,100,100); + } + + void send(Event::Type type, int id, int x, Event::MouseButton button=Event::ButtonLeft) { + MouseEvent e(x,20,button,Event::M_NoModifier,0,id,type); + switch(type) { + case Event::MouseDown: dispatcher.dispatchMouseDown(root,e); break; + case Event::MouseUp: dispatcher.dispatchMouseUp(root,e); break; + case Event::MouseMove: dispatcher.dispatchMouseMove(root,e); break; + default: FAIL(); + } + } + }; +} + +TEST_F(PointerDispatch,SeparateWidgets) { + for(int first:{0,1}) { + left.events.clear(); + right.events.clear(); + send(Event::MouseDown,0,20); + send(Event::MouseDown,1,120); + send(Event::MouseMove,0,150); + send(Event::MouseMove,1,50); + send(Event::MouseUp,first,50); + send(Event::MouseMove,1-first,150); + send(Event::MouseUp,1-first,150); + + for(int id:{0,1}) { + auto& events = id==0 ? left.events : right.events; + ASSERT_EQ(events.size(),id==first ? 3u : 4u); + EXPECT_EQ(events.front().type(),Event::MouseDown); + EXPECT_EQ(events[1].type(),Event::MouseDrag); + EXPECT_EQ(events[1].x,id==0 ? 150 : -50); + EXPECT_EQ(events.back().type(),Event::MouseUp); + for(auto& e:events) + EXPECT_EQ(e.mouseID,id); + } + } + } + +TEST_F(PointerDispatch,SameWidget) { + send(Event::MouseDown,0,20); + send(Event::MouseDown,1,30); + send(Event::MouseMove,0,150); + send(Event::MouseMove,1,160); + send(Event::MouseUp,1,160); + send(Event::MouseUp,0,150); + EXPECT_EQ(left.doubleClicks,0); + ASSERT_EQ(left.events.size(),6u); + EXPECT_EQ(left.events[2].mouseID,0); + EXPECT_EQ(left.events[3].mouseID,1); + EXPECT_EQ(left.events[4].mouseID,1); + EXPECT_EQ(left.events[5].mouseID,0); + EXPECT_EQ(left.events[4].type(),Event::MouseUp); + EXPECT_EQ(left.events[5].type(),Event::MouseUp); + } + +TEST_F(PointerDispatch,MouseButtonsAndDoubleClick) { + send(Event::MouseDown,0,20); + send(Event::MouseDown,0,120,Event::ButtonRight); + send(Event::MouseMove,0,180); + send(Event::MouseUp,0,180); + send(Event::MouseMove,0,20); + send(Event::MouseUp,0,20,Event::ButtonRight); + ASSERT_EQ(left.events.size(),3u); + ASSERT_EQ(right.events.size(),3u); + EXPECT_EQ(left.events[1].button,Event::ButtonLeft); + EXPECT_EQ(right.events[1].button,Event::ButtonRight); + send(Event::MouseDown,0,120,Event::ButtonRight); + EXPECT_EQ(right.doubleClicks,1); + send(Event::MouseUp,0,120,Event::ButtonRight); + EXPECT_EQ(right.events.back().type(),Event::MouseUp); + } + +TEST_F(PointerDispatch,MouseButtonsReverseOrder) { + send(Event::MouseDown,0,120,Event::ButtonRight); + send(Event::MouseDown,0,20); + send(Event::MouseMove,0,180); + ASSERT_EQ(left.events.size(),2u); + ASSERT_EQ(right.events.size(),1u); + EXPECT_EQ(left.events.back().type(),Event::MouseDrag); + EXPECT_EQ(left.events.back().button,Event::ButtonLeft); + send(Event::MouseUp,0,180); + send(Event::MouseUp,0,180,Event::ButtonRight); + } + +TEST_F(PointerDispatch,RepeatedDownReplacesCapture) { + send(Event::MouseDown,0,20); + send(Event::MouseDown,0,120); + send(Event::MouseMove,0,20); + send(Event::MouseUp,0,20); + send(Event::MouseUp,0,20); + ASSERT_EQ(left.events.size(),1u); + ASSERT_EQ(right.events.size(),3u); + EXPECT_EQ(right.events[1].type(),Event::MouseDrag); + EXPECT_EQ(right.events[2].type(),Event::MouseUp); + } + +TEST_F(PointerDispatch,UncapturedPointer) { + send(Event::MouseDown,0,20); + send(Event::MouseUp,1,120); + send(Event::MouseMove,1,120); + send(Event::MouseMove,0,120); + send(Event::MouseUp,0,120); + ASSERT_EQ(left.events.size(),3u); + EXPECT_EQ(left.events[1].type(),Event::MouseDrag); + EXPECT_EQ(left.events[2].type(),Event::MouseUp); + ASSERT_EQ(right.events.size(),1u); + EXPECT_EQ(right.events[0].type(),Event::MouseMove); + EXPECT_EQ(right.events[0].mouseID,1); + } + +TEST_F(PointerDispatch,NestedReleaseDuringDown) { + left.onDown = [&](MouseEvent&) { send(Event::MouseUp,0,20); }; + send(Event::MouseDown,1,120); + send(Event::MouseDown,0,20); + send(Event::MouseMove,0,150); + send(Event::MouseUp,1,150); + ASSERT_EQ(left.events.size(),1u); + ASSERT_EQ(right.events.size(),3u); + EXPECT_EQ(right.events[1].type(),Event::MouseMove); + EXPECT_EQ(right.events[1].mouseID,0); + EXPECT_EQ(right.events[2].type(),Event::MouseUp); + EXPECT_EQ(right.events[2].mouseID,1); + } + +TEST_F(PointerDispatch,NestedReleaseDuringDrag) { + left.onDrag = [&](MouseEvent& e) { + send(Event::MouseUp,0,20); + e.ignore(); + }; + send(Event::MouseDown,0,20); + send(Event::MouseMove,0,120); + ASSERT_EQ(left.events.size(),3u); + EXPECT_EQ(left.events.back().type(),Event::MouseUp); + ASSERT_EQ(right.events.size(),1u); + EXPECT_EQ(right.events[0].type(),Event::MouseMove); + } + +TEST_F(PointerDispatch,NestedDownDuringDrag) { + left.onDrag = [&](MouseEvent& e) { + send(Event::MouseDown,1,120); + e.ignore(); + }; + send(Event::MouseDown,0,20); + send(Event::MouseMove,0,120); + ASSERT_EQ(left.events.size(),3u); + EXPECT_EQ(left.events.back().type(),Event::MouseMove); + EXPECT_EQ(left.events.back().mouseID,0); + send(Event::MouseUp,0,120); + send(Event::MouseMove,1,20); + send(Event::MouseUp,1,20); + ASSERT_EQ(right.events.size(),3u); + EXPECT_EQ(right.events[1].type(),Event::MouseDrag); + EXPECT_EQ(right.events[1].mouseID,1); + EXPECT_EQ(right.events[2].type(),Event::MouseUp); + } + +TEST_F(PointerDispatch,NestedDownDuringRelease) { + left.onUp = [&](MouseEvent&) { send(Event::MouseDown,0,120); }; + send(Event::MouseDown,0,20); + send(Event::MouseUp,0,20); + send(Event::MouseMove,0,20); + send(Event::MouseUp,0,20); + ASSERT_EQ(left.events.size(),2u); + ASSERT_EQ(right.events.size(),3u); + EXPECT_EQ(right.events[1].type(),Event::MouseDrag); + EXPECT_EQ(right.events[2].type(),Event::MouseUp); + } + +TEST_F(PointerDispatch,DeletedCapture) { + send(Event::MouseDown,0,20); + send(Event::MouseDown,1,120); + delete &left; + send(Event::MouseMove,0,120); + send(Event::MouseUp,0,120); + send(Event::MouseMove,1,20); + send(Event::MouseUp,1,20); + ASSERT_EQ(right.events.size(),4u); + EXPECT_EQ(right.events[1].type(),Event::MouseMove); + EXPECT_EQ(right.events[1].mouseID,0); + EXPECT_EQ(right.events[2].type(),Event::MouseDrag); + EXPECT_EQ(right.events[2].mouseID,1); + EXPECT_EQ(right.events[3].type(),Event::MouseUp); + EXPECT_EQ(right.events[3].mouseID,1); + } + +TEST_F(PointerDispatch,DeletedDuringDown) { + struct ClosingWidget : Widget { + void mouseDownEvent(MouseEvent&) override { delete this; } + }; + auto& closing = root.addWidget(new ClosingWidget()); + closing.setGeometry(0,0,100,100); + send(Event::MouseDown,1,120); + send(Event::MouseDown,0,20); + send(Event::MouseMove,0,120); + send(Event::MouseUp,1,120); + ASSERT_EQ(right.events.size(),3u); + EXPECT_EQ(right.events[1].type(),Event::MouseMove); + EXPECT_EQ(right.events[1].mouseID,0); + EXPECT_EQ(right.events[2].type(),Event::MouseUp); + EXPECT_EQ(right.events[2].mouseID,1); + }