axmolengine / axmol

Axmol Engine – A Multi-platform Engine for Desktop, XBOX (UWP) and Mobile games. (A fork of Cocos2d-x-4.0)
https://axmol.dev
MIT License
923 stars 205 forks source link

TabControl #1221

Closed Arvant closed 1 year ago

Arvant commented 1 year ago

when I create a layer and add Tabcontrol to it. if set the layout listener setswallow to true and return true in touch began. the tab bar not work. but if add button to this layout the button work.

` auto listener = EventListenerTouchOneByOne::create(); listener->setSwallowTouches(true);

listener->onTouchBegan = [](Touch* t, Event* e) {
    return true;
};
listener->onTouchMoved = [](Touch* t, Event* e) {
};
listener->onTouchEnded = [](Touch* t, Event* e) {
};

auto layerColorPopup = ax::LayerColor::create(Color4B(0, 0, 0, 190));

this->addChild(layerColorPopup);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, layerColorPopup);

 auto layerColorPopup = ax::LayerColor::create(Color4B(0, 0, 0, 190));

this->addChild(layerColorPopup);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, layerColorPopup);

auto bg_tab_continers = Sprite::create("equipment/brown.png");
bg_tab_continers->setAnchorPoint(Vec2::ANCHOR_MIDDLE_BOTTOM);
bg_tab_continers->setPosition(Vec2(200, 0));

auto sp_header_tab = Sprite::create("equipment/tabs.png");

auto tab = ax::ui::TabControl::create();
tab->setGlobalZOrder(44444);
tab->setAnchorPoint(Vec2::ANCHOR_BOTTOM_LEFT);
tab->setPosition(Vec2(5, 10));
tab->setHeaderHeight(sp_header_tab->getContentSize().height);
tab->setHeaderWidth((bg_tab_continers->getContentSize().width / 3));
tab->setHeaderDockPlace(ax::ui::TabControl::Dock::TOP);
tab->setHeaderSelectedZoom(0.0f);

auto button = ax::ui::Button::create("equipment/tabs.png");

button->setTitleText("Button Text");
button->addTouchEventListener([&](Ref* sender, ax::ui::Widget::TouchEventType type) {
    switch (type)
    {
    case ui::Widget::TouchEventType::BEGAN:
        break;
    case ui::Widget::TouchEventType::ENDED:
        AXLOG("test");
        break;
    default:
        break;
    }
    });
button->setPosition(layerColorPopup->getContentSize() / 2);
layerColorPopup->addChild(button);

auto header1 = ax::ui::TabHeader::create("head", "equipment/tabs.png", "equipment/tabs_select.png");

header1->setTouchEnabled(false);
header1->setTouchEnabled(true);
header1->addTouchEventListener([&](Ref* sender, ax::ui::Widget::TouchEventType type) {
    switch (type)
    {
    case ui::Widget::TouchEventType::BEGAN:
        break;
    case ui::Widget::TouchEventType::ENDED:
        AXLOG("Touch");
        break;
    default:
        break;
    }
    });

header1->setZoomScale(0.0f);

auto container1 = ax::ui::Layout::create();
container1->setContentSize(bg_tab_continers->getContentSize());
/////////-------------------------------------------------- container1 atechment

tab->insertTab(0, header1, container1);
//tab->insertTab(2, header3, container3);

tab->setSelectTab(0);
this->addChild(tab);

`

aismann commented 1 year ago

@Arvant have you seen the cpp-test example? Maybe you can comparing your implementation with this one first?

Arvant commented 1 year ago

@Arvant have you seen the cpp-test example? Maybe you can comparing your implementation with this one first?

yes i see it. But this problem has been there for a long time in cocos2d

aismann commented 1 year ago

yes i see it. But this problem has been there for a long time in cocos2d

? What are you mean? Have you a link to this cocos2d issue too? Is the cpp-test example not working/usable for your example/situation?

Arvant commented 1 year ago

yes i see it. But this problem has been there for a long time in cocos2d

? What are you mean? Have you a link to this cocos2d issue too? Is the cpp-test example not working/usable for your example/situation?

i early in cocos2d-x have this problem and I create custom tab for solve this. now i test above code in axmol and see the problem is exist too. this case not use in test project and you need to create simple project and insert my code in project.

aismann commented 1 year ago

this case not use in test project and you need to create simple project and insert my code in project.

Sprite::create("equipment/brown.png");

@Arvant can you provide a working example?);

Arvant commented 1 year ago

this case not use in test project and you need to create simple project and insert my code in project.

Sprite::create("equipment/brown.png");

@Arvant can you provide a working example?);

yes I upload test. test repo

aismann commented 1 year ago

Remove this line: _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, layerColorPopup);

Arvant commented 1 year ago

Remove this line: _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, layerColorPopup);

if you went to create popup you need this listener to Swallow touches.

aismann commented 1 year ago

Sorry I'm out. Your example works now. Thats was the challenge.

aismann commented 1 year ago

@Arvant Please close this issue. Thanks

rh101 commented 1 year ago

If you're creating a popup with the tab control on it, then this line:

this->addChild(tab);

should be this:

layerColorPopup->addChild(tab);

This wouldn't be causing your issue though, since the touch event is hitting the layerColorPopup before it gets to the tab control, which is actually strange.

rh101 commented 1 year ago

@Arvant Please close this issue. Thanks

The problem isn't solved, as there does seem to be some issue with how the touches are being handled.

aismann commented 1 year ago

Please provide an example. And please compare the cpp-test example with your implementation too.

rh101 commented 1 year ago

Please provide an example. And please compare the cpp-test example with your implementation too.

The code provided by the OP, while a little messy and harder to follow, is actually the correct example.

This is the structure:

Scene
  |-- PopupLayer
         |--TabControl
               |-- TabHeader1
               |-- TabHeader2 
               |-- TabHeaderN... etc

The problem seems to be that the touch input is being received by the PopupLayer, then TabControl (Widget)/TabHeader (Widget), at least from what I can see in the output. From my understanding, the highest layers should be getting input before the lower layers, but that isn't what seems to be happening here.

rh101 commented 1 year ago

When a touch event occurs, the method EventDispatcher::sortEventListenersOfSceneGraphPriority() is eventually called to sort the event listeners. In here is where the problem first appears. The UIButton is first in the list, then the layerColorPopup, then the HelloWorld scene, then any tab control header widgets. This causes the button touch handler to be called first, then the layerColor, then the scene, and finally the tab header widgets. That is the reason why the button touch works, but the tab controls do not.

The tab control widgets should be higher up in priority, and ahead of the layerColorPopup, but they are not. I am not sure why this is happening as of yet, and unfortunately I don't have any more time at the moment to look into it.

@Arvant Note, get rid of tab->setGlobalZOrder(44444); from the example you provided, because that is not required, and just adds more confusion to the issue.

EDIT: I have a feeling that it has something to do with child nodes, and how they are added when they are children of Widget nodes. The addProtectedChild that is used in Widgets adds child objects to a _protectedChildren list, yet the sorting used by methods called from sortEventListenersOfSceneGraphPriority use the _children list. Since the protected child nodes are missing from the _children list, they aren't included in the priority list. Take a look at EventDispatcher::visitTarget(Node* node, bool isRootNode) to see what I am referring to.

@Arvant The reason the button can still be pressed is because it happens to be a child of the layerColorPopup, added via addChild, so it exists in the _children list. The tab control itself is added via addChild, but it's not actually what receives the touch input, as the TabHeader widgets are the the ones that get the touch input, and they are added to the TabControl via addProtectedChild.

rh101 commented 1 year ago

@Arvant There is a work-around for this, but you would need to be aware of a few things.

Let's say you create the following: Scene is at global Z of 0. Popup layer is at a global Z of 100 (arbitrary value, as long as it's greater than the scene global Z value).

When you create the popup, also create another layer (let's call it TouchBlockLayer) and give it a global Z of less than the popup layer (even 0), and add a touch event listener to it. Then, add this layer to the scene.

What happens now is that your popup will work as intended, but no touch events will propagate through the TouchBlockLayer to the scene below it.

So it will be like this:

Scene (globalZ = 0)
  |-- TouchBlockLayer (globalZ = 0 to 99.9999 etc)
  |-- PopupLayer (globalZ = 100)
         |--Popup child (like tab control etc) (globalZ = 100)

What you need to look out for is that all children of the popup layer must be at the same or greater global Z values than the popup. It's best to just make them all the same as the popup global Z unless you have a specific reason not to.

This will work, as this is actually the method I use to achieve something like what you're trying to do. When you remove the popup, also remove the TouchBlockLayer.

Arvant commented 1 year ago

@Arvant There is a work-around for this, but you would need to be aware of a few things.

Let's say you create the following: Scene is at global Z of 0. Popup layer is at a global Z of 100 (arbitrary value, as long as it's greater than the scene global Z value).

When you create the popup, also create another layer (let's call it TouchBlockLayer) and give it a global Z of less than the popup layer (even 0), and add a touch event listener to it. Then, add this layer to the scene.

What happens now is that your popup will work as intended, but no touch events will propagate through the TouchBlockLayer to the scene below it.

So it will be like this:

Scene (globalZ = 0)
  |-- TouchBlockLayer (globalZ = 0 to 99.9999 etc)
  |-- PopupLayer (globalZ = 100)
         |--Popup child (like tab control etc) (globalZ = 100)

What you need to look out for is that all children of the popup layer must be at the same or greater global Z values than the popup. It's best to just make them all the same as the popup global Z unless you have a specific reason not to.

This will work, as this is actually the method I use to achieve something like what you're trying to do. When you remove the popup, also remove the TouchBlockLayer.

rh101 I test this way before but it not work for me. I have forced to use layer and button to simulate tab control in popups. I have this problem in cocos2d-x 4 too. I debug cocos2d-x touch listener like you and see that it have problem in touch priority. I work with global Z order and fixed touch priority too but not work that.

rh101 commented 1 year ago

@Arvant The method I described with global Z does in fact work, since I've been using it for years, but, it may not be ideal, since it requires tracking the global Z etc.

I think I've found a more reliable solution to this issue, and it seems to be working. A PR will be up soon. Even if the PR is not merged in, you can just merge in the changes with your own copy of the engine code. The changes are trivial.

rh101 commented 1 year ago

@Arvant The PR #1224 is up. Merge it in to your own code to check if it works.

rh101 commented 1 year ago

@Arvant The PR was simplified, as it is better to keep the functionality contained in EventDispatcher rather than add unnecessary functionality to Node and ProtectedNode.

aismann commented 1 year ago

@rh101 Thanks for this PR.

Arvant commented 1 year ago

@rh101 it work perfect. Thanks for this PR.

rh101 commented 1 year ago

@Arvant Please close this issue since the PR with the fix has been merged into the dev branch.