diff --git a/gamedata/scripts/lua_help_ex.script b/gamedata/scripts/lua_help_ex.script index 97d020525..46de1c8b5 100644 --- a/gamedata/scripts/lua_help_ex.script +++ b/gamedata/scripts/lua_help_ex.script @@ -264,6 +264,7 @@ void AddHeader(string str) // Adds a non-selectable, colored title row (used to group items in a merged context menu) CUIPropertiesBox AddSubmenu(string label) // Adds a "label >" row whose submenu expands on hover; returns the child box to populate (AddItem/AddHeader/nested AddSubmenu) bool AddItem(string str, u32 id) // Adds a selectable item row carrying a u32 id (read back via GetSelectedItem():GetTAG() / map_spot_menu_property_clicked prop_id); lets a menu carry duplicate labels + void ShowAt(Frect parent_rect, vector2 point) // Opens the box at `point`, kept inside `parent_rect`; both in the box's PARENT space. AttachChild and AutoUpdateSize() first (submenus too) } class CUIListBoxItem : CUIFrameLineWnd { diff --git a/src/xrGame/ui/UIActorMenu.cpp b/src/xrGame/ui/UIActorMenu.cpp index 2e1c901d8..1bef6f35a 100644 --- a/src/xrGame/ui/UIActorMenu.cpp +++ b/src/xrGame/ui/UIActorMenu.cpp @@ -851,7 +851,6 @@ void CUIActorMenu::CallMessageBoxOK(LPCSTR text) void CUIActorMenu::ResetMode() { ClearAllLists(); - m_pMouseCapturer = NULL; m_UIPropertiesBox->Hide(); SetCurrentItem(NULL); } diff --git a/src/xrGame/ui/UIActorMenuInventory.cpp b/src/xrGame/ui/UIActorMenuInventory.cpp index d06899d75..b6ad0d306 100644 --- a/src/xrGame/ui/UIActorMenuInventory.cpp +++ b/src/xrGame/ui/UIActorMenuInventory.cpp @@ -451,7 +451,6 @@ void CUIActorMenu::InitCellForSlot(u16 slot_idx) void CUIActorMenu::InitInventoryContents(CUIDragDropListEx* pBagList) { ClearAllLists(); - m_pMouseCapturer = NULL; m_UIPropertiesBox->Hide(); SetCurrentItem(NULL); diff --git a/src/xrGame/ui/UIDragDropListEx.cpp b/src/xrGame/ui/UIDragDropListEx.cpp index 18942330a..9ec021d32 100644 --- a/src/xrGame/ui/UIDragDropListEx.cpp +++ b/src/xrGame/ui/UIDragDropListEx.cpp @@ -155,8 +155,8 @@ void CUIDragDropListEx::DestroyDragItem() { if (m_selected_item && m_drag_item && m_drag_item->ParentItem() == m_selected_item) { - VERIFY(GetParent()->GetMouseCapturer()==m_drag_item); - GetParent()->SetCapture(NULL, false); + VERIFY(m_drag_item->IsMouseCapturer()); + GetParent()->SetCapture(m_drag_item, false); delete_data(m_drag_item); } diff --git a/src/xrGame/ui/UIFixedScrollBar.cpp b/src/xrGame/ui/UIFixedScrollBar.cpp index cca5e0edf..07753aff7 100644 --- a/src/xrGame/ui/UIFixedScrollBar.cpp +++ b/src/xrGame/ui/UIFixedScrollBar.cpp @@ -177,7 +177,7 @@ bool CUIFixedScrollBar::OnMouseAction(float x, float y, EUIMessages mouse_action return true; case WINDOW_MOUSE_MOVE: { - bool im_capturer = (GetMouseCapturer() == m_ScrollBox); + bool im_capturer = m_ScrollBox->IsMouseCapturer(); bool cursor_over = false; Fvector2 cursor_pos = GetUICursor().GetCursorPosition(); Frect box_rect; diff --git a/src/xrGame/ui/UIMpTradeWnd.cpp b/src/xrGame/ui/UIMpTradeWnd.cpp index be872aab2..17f1d9a51 100644 --- a/src/xrGame/ui/UIMpTradeWnd.cpp +++ b/src/xrGame/ui/UIMpTradeWnd.cpp @@ -232,8 +232,6 @@ void CUIMpTradeWnd::Show(bool status) if (status) { - m_pMouseCapturer = NULL; - m_static_information->SetText(""); m_static_money_change->SetText(""); } diff --git a/src/xrGame/ui/UIPropertiesBox.cpp b/src/xrGame/ui/UIPropertiesBox.cpp index f694e6949..5690dc5a3 100644 --- a/src/xrGame/ui/UIPropertiesBox.cpp +++ b/src/xrGame/ui/UIPropertiesBox.cpp @@ -141,15 +141,15 @@ void CUIPropertiesBox::HideActiveSubmenu() m_submenu_close_at = 0; } -bool CUIPropertiesBox::CursorOverTree() +CUIPropertiesBox* CUIPropertiesBox::BoxUnderCursor() { + if (m_active_submenu && m_active_submenu->IsShown()) + if (CUIPropertiesBox* deeper = m_active_submenu->BoxUnderCursor()) + return deeper; + Frect r; GetAbsoluteRect(r); - if (r.in(GetUICursor().GetCursorPosition())) - return true; - if (m_active_submenu && m_active_submenu->IsShown()) - return m_active_submenu->CursorOverTree(); - return false; + return r.in(GetUICursor().GetCursorPosition()) ? this : NULL; } void CUIPropertiesBox::ClearSubmenus() @@ -251,7 +251,8 @@ void CUIPropertiesBox::Show(const Frect& parent_rect, const Fvector2& point) ResetAll(); - GetParent()->SetCapture(this, true); + if (!m_parent_menu) + GetParent()->SetCapture(this, true); m_UIListWnd.Reset(); float pad_h = m_UIListWnd.GetPadSize().y; @@ -264,66 +265,49 @@ void CUIPropertiesBox::Hide() CUIWindow::Show(false); CUIWindow::Enable(false); - m_pMouseCapturer = NULL; - - if (GetParent()->GetMouseCapturer() == this) - { - if (m_parent_menu && m_parent_menu->IsShown()) - GetParent()->SetCapture(m_parent_menu, true); - else - GetParent()->SetCapture(this, false); - } + ReleaseMouseCapture(); HideActiveSubmenu(); } bool CUIPropertiesBox::OnMouseAction(float x, float y, EUIMessages mouse_action) { - bool cursor_on_box; + if (m_parent_menu) + return HandleMouse(x, y, mouse_action); + CUIPropertiesBox* target = BoxUnderCursor(); - if (x >= 0 && x < GetWidth() && y >= 0 && y < GetHeight()) - cursor_on_box = true; - else - cursor_on_box = false; - - - CUIPropertiesBox* root = this; - while (root->m_parent_menu) - root = root->m_parent_menu; - - if (mouse_action == WINDOW_LBUTTON_DOWN && !cursor_on_box) - { - root->Hide(); - return true; - } - if (mouse_action == WINDOW_RBUTTON_DOWN && !cursor_on_box) + if (target && target != this) { - root->Hide(); + Frect r; + target->GetAbsoluteRect(r); + Fvector2 cur = GetUICursor().GetCursorPosition(); + return target->HandleMouse(cur.x - r.x1, cur.y - r.y1, mouse_action); } - if (mouse_action == WINDOW_MOUSE_WHEEL_DOWN || mouse_action == WINDOW_MOUSE_WHEEL_UP) + + if (!target) // outside the whole tree { - Fvector2 cur = GetUICursor().GetCursorPosition(); - CUIPropertiesBox* target = this; - for (CUIPropertiesBox* b = root; b; - b = (b->m_active_submenu && b->m_active_submenu->IsShown()) ? b->m_active_submenu : NULL) + if (mouse_action == WINDOW_LBUTTON_DOWN || mouse_action == WINDOW_RBUTTON_DOWN) { - Frect r; - b->GetAbsoluteRect(r); - if (r.in(cur)) - target = b; + Hide(); + return true; } - if (target->m_UIListWnd.GetPadSize().y > target->m_UIListWnd.GetHeight()) - target->m_UIListWnd.OnMouseAction(x, y, mouse_action); - return true; } - bool res = inherited::OnMouseAction(x, y, mouse_action); + return HandleMouse(x, y, mouse_action); +} - if (IsShown() && GetParent() && GetParent()->GetMouseCapturer() != this) - GetParent()->SetCapture(this, true); +// Input handling for one box, after routing. Coordinates are relative to this box. +bool CUIPropertiesBox::HandleMouse(float x, float y, EUIMessages mouse_action) +{ + if (mouse_action == WINDOW_MOUSE_WHEEL_DOWN || mouse_action == WINDOW_MOUSE_WHEEL_UP) + { + if (m_UIListWnd.GetPadSize().y > m_UIListWnd.GetHeight()) + m_UIListWnd.OnMouseAction(x, y, mouse_action); + return true; + } - return res; + return inherited::OnMouseAction(x, y, mouse_action); } void CUIPropertiesBox::AutoUpdateSize() @@ -370,7 +354,7 @@ void CUIPropertiesBox::Update() { Frect ir; m_active_sub_item->GetAbsoluteRect(ir); - bool inside = ir.in(GetUICursor().GetCursorPosition()) || m_active_submenu->CursorOverTree(); + bool inside = ir.in(GetUICursor().GetCursorPosition()) || m_active_submenu->BoxUnderCursor(); if (inside) { m_submenu_close_at = 0; diff --git a/src/xrGame/ui/UIPropertiesBox.h b/src/xrGame/ui/UIPropertiesBox.h index 90f3739ff..6d9798e53 100644 --- a/src/xrGame/ui/UIPropertiesBox.h +++ b/src/xrGame/ui/UIPropertiesBox.h @@ -62,7 +62,9 @@ class CUIPropertiesBox : void ShowSubMenuForItem(CUIListBoxItem* item); void HideActiveSubmenu(); void ClearSubmenus(); - bool CursorOverTree(); + + CUIPropertiesBox* BoxUnderCursor(); + bool HandleMouse(float x, float y, EUIMessages mouse_action); DECLARE_SCRIPT_REGISTER_FUNCTION }; diff --git a/src/xrGame/ui/UIPropertiesBox_script.cpp b/src/xrGame/ui/UIPropertiesBox_script.cpp index 2ae44db2c..92ab049d4 100644 --- a/src/xrGame/ui/UIPropertiesBox_script.cpp +++ b/src/xrGame/ui/UIPropertiesBox_script.cpp @@ -15,6 +15,7 @@ void CUIPropertiesBox::script_register(lua_State* L) .def("RemoveItem", &CUIPropertiesBox::RemoveItemByTAG) .def("RemoveAll", &CUIPropertiesBox::RemoveAll) .def("Show", (void(CUIPropertiesBox::*)(int, int))&CUIPropertiesBox::Show) + .def("ShowAt", (void(CUIPropertiesBox::*)(const Frect&, const Fvector2&))&CUIPropertiesBox::Show) .def("Hide", &CUIPropertiesBox::Hide) .def("GetSelectedItem", &CUIPropertiesBox::GetClickedItem) .def("AutoUpdateSize", &CUIPropertiesBox::AutoUpdateSize) diff --git a/src/xrGame/ui/UIScrollBox.cpp b/src/xrGame/ui/UIScrollBox.cpp index 30dfebfed..47042c6a6 100644 --- a/src/xrGame/ui/UIScrollBox.cpp +++ b/src/xrGame/ui/UIScrollBox.cpp @@ -22,7 +22,7 @@ bool CUIScrollBox::OnMouseAction(float x, float y, EUIMessages mouse_action) cursor_over = true; } - bool im_capturer = (GetParent()->GetMouseCapturer() == this); + bool im_capturer = IsMouseCapturer(); if (mouse_action == WINDOW_LBUTTON_DOWN || mouse_action == WINDOW_LBUTTON_DB_CLICK) { diff --git a/src/xrGame/ui/UIWindow.cpp b/src/xrGame/ui/UIWindow.cpp index 5840c89f6..4af5cf4c1 100644 --- a/src/xrGame/ui/UIWindow.cpp +++ b/src/xrGame/ui/UIWindow.cpp @@ -105,9 +105,36 @@ void CUIWindow::ResetPPMode() } } +// Windows that have taken the mouse, oldest first; the top one receives all mouse input. Nesting is +// a transient grab (a scrollbar drag) on top of a lasting one (an open popup), so a release restores +// the previous holder instead of leaving nobody in charge. +static xr_vector s_capture_stack; + +static void erase_capture(CUIWindow* w) +{ + auto it = std::find(s_capture_stack.begin(), s_capture_stack.end(), w); + if (it != s_capture_stack.end()) + s_capture_stack.erase(it); +} + +static bool capture_inside(CUIWindow* c, CUIWindow* w) +{ + for (CUIWindow* p = c; p; p = p->GetParent()) + if (p == w) + return true; + return false; +} + +static void erase_capture_subtree(CUIWindow* w) +{ + s_capture_stack.erase( + std::remove_if(s_capture_stack.begin(), s_capture_stack.end(), + [w](CUIWindow* c) { return capture_inside(c, w); }), + s_capture_stack.end()); +} + CUIWindow::CUIWindow() : m_pParentWnd(NULL), - m_pMouseCapturer(NULL), m_pMessageTarget(NULL), m_pKeyboardCapturer(NULL), m_bAutoDelete(false), @@ -135,6 +162,8 @@ CUIWindow::~CUIWindow() { VERIFY(!(GetParent()&&IsAutoDelete())); + erase_capture_subtree(this); + //if (m_pHint) // xr_delete(m_pHint); @@ -252,8 +281,7 @@ void CUIWindow::DetachChild(CUIWindow* pChild) if (NULL == pChild) return; - if (m_pMouseCapturer == pChild) - SetCapture(pChild, false); + erase_capture_subtree(pChild); //. SafeRemoveChild (pChild); WINDOW_LIST_it it = std::find(m_ChildWndList.begin(), m_ChildWndList.end(), pChild); @@ -321,6 +349,24 @@ bool CUIWindow::OnMouseAction(float x, float y, EUIMessages mouse_action) if (GetParent() == NULL) { + // the window that captured the mouse gets every message, wherever the cursor is + CUIWindow* cap = MouseCapturer(); + if (cap && cap != this) + { + CUIWindow* cap_root = cap; + while (cap_root->GetParent()) + cap_root = cap_root->GetParent(); + + if (cap_root == this || cap_root == cap) // our tree, or an unattached capturer + { + Frect cap_rect; + cap->GetAbsoluteRect(cap_rect); + Fvector2 cur = GetUICursor().GetCursorPosition(); + cap->OnMouseAction(cur.x - cap_rect.left, cur.y - cap_rect.top, mouse_action); + return true; + } + } + if (!wndRect.in(cursor_pos)) return false; //получить координаты относительно окна @@ -328,17 +374,6 @@ bool CUIWindow::OnMouseAction(float x, float y, EUIMessages mouse_action) cursor_pos.y -= wndRect.top; } - - //если есть дочернее окно,захватившее мышь, то - //сообщение направляем ему сразу - if (m_pMouseCapturer) - { - m_pMouseCapturer->OnMouseAction(cursor_pos.x - m_pMouseCapturer->GetWndRect().left, - cursor_pos.y - m_pMouseCapturer->GetWndRect().top, - mouse_action); - return true; - } - // handle any action switch (mouse_action) { @@ -452,25 +487,34 @@ void CUIWindow::OnFocusLost() //ему в независимости от того где мышь void CUIWindow::SetCapture(CUIWindow* pChildWindow, bool capture_status) { - if (GetParent()) - { - GetParent()->SetCapture(this, capture_status); - } - if (capture_status) { + if (!pChildWindow) + return; + //оповестить дочернее окно о потере фокуса мыши - if (NULL != m_pMouseCapturer) - m_pMouseCapturer->SendMessage(this, WINDOW_MOUSE_CAPTURE_LOST); + if (!s_capture_stack.empty() && s_capture_stack.back() != pChildWindow) + s_capture_stack.back()->SendMessage(this, WINDOW_MOUSE_CAPTURE_LOST); - m_pMouseCapturer = pChildWindow; + erase_capture(pChildWindow); // re-capture moves it to the top rather than stacking twice + s_capture_stack.push_back(pChildWindow); } else { - m_pMouseCapturer = NULL; + erase_capture(pChildWindow); } } +CUIWindow* CUIWindow::MouseCapturer() +{ + return s_capture_stack.empty() ? NULL : s_capture_stack.back(); +} + +void CUIWindow::ReleaseMouseCapture() +{ + erase_capture_subtree(this); +} + //реакция на клавиатуру bool CUIWindow::OnKeyboardAction(int dik, EUIMessages keyboard_action) @@ -589,7 +633,6 @@ CUIWindow* CUIWindow::GetChildMouseHandler() //для перевода окна и потомков в исходное состояние void CUIWindow::Reset() { - m_pMouseCapturer = NULL; } void CUIWindow::ResetAll() diff --git a/src/xrGame/ui/UIWindow.h b/src/xrGame/ui/UIWindow.h index 881f3763b..6329919ac 100644 --- a/src/xrGame/ui/UIWindow.h +++ b/src/xrGame/ui/UIWindow.h @@ -168,8 +168,14 @@ class CUIWindow : public CUISimpleWindow //захватить/освободить мышь окном //сообщение посылается дочерним окном родительскому + // Capture is global, not per-window: one stack of windows that have taken the mouse, topmost + // wins, and releasing restores the one underneath (a popup keeps its capture across a scrollbar + // drag inside it). A capturer need not be a child of the receiver, or attached at all -- + // CUIDragItem has no parent. void SetCapture(CUIWindow* pChildWindow, bool capture_status); - CUIWindow* GetMouseCapturer() { return m_pMouseCapturer; } + static CUIWindow* MouseCapturer(); + bool IsMouseCapturer() { return MouseCapturer() == this; } + void ReleaseMouseCapture(); //окошко, которому пересылаются сообщения, //если NULL, то шлем на GetParent() @@ -307,9 +313,6 @@ class CUIWindow : public CUISimpleWindow //указатель на родительское окно CUIWindow* m_pParentWnd; - //дочернее окно которое, захватило ввод мыши - CUIWindow* m_pMouseCapturer; - //дочернее окно которое, захватило ввод клавиатуры CUIWindow* m_pKeyboardCapturer;