From 3d614e4e8141059ba71c2850e8324c23bc4859d1 Mon Sep 17 00:00:00 2001 From: Xottab-DUTY Date: Mon, 6 Jan 2025 12:26:17 +0300 Subject: [PATCH] Working UI focus system (#943) --- src/xrGame/UIDialogHolder.cpp | 52 ++++-- src/xrGame/UIDialogHolder.h | 3 +- src/xrGame/ui/UIDialogWnd.h | 10 +- src/xrUICore/Buttons/UIButton.cpp | 8 +- src/xrUICore/Buttons/UIButton.h | 1 + src/xrUICore/ComboBox/UIComboBox.cpp | 9 +- src/xrUICore/ComboBox/UIComboBox.h | 1 + src/xrUICore/EditBox/UICustomEdit.cpp | 10 +- src/xrUICore/TrackBar/UITrackBar.cpp | 9 +- src/xrUICore/TrackBar/UITrackBar.h | 2 + src/xrUICore/Windows/UIWindow.cpp | 31 +--- src/xrUICore/Windows/UIWindow.h | 26 +-- src/xrUICore/Windows/UIWindow_script.cpp | 11 +- src/xrUICore/ui_base.h | 3 + src/xrUICore/ui_focus.cpp | 224 ++++++++++++++--------- src/xrUICore/ui_focus.h | 39 ++-- 16 files changed, 245 insertions(+), 194 deletions(-) diff --git a/src/xrGame/UIDialogHolder.cpp b/src/xrGame/UIDialogHolder.cpp index 4d229441767..3f33e96107e 100644 --- a/src/xrGame/UIDialogHolder.cpp +++ b/src/xrGame/UIDialogHolder.cpp @@ -55,7 +55,7 @@ void CDialogHolder::StartMenu(CUIDialogWnd* pDialog, bool bDoHideIndicators) CurrentGameUI()->ShowGameIndicators(false); } } - SetFocused(nullptr); + UI().Focus().SetFocused(nullptr); pDialog->SetHolder(this); if (pDialog->NeedCursor()) @@ -248,6 +248,9 @@ void CDialogHolder::OnFrame() (*it).wnd->Update(); } + if (m_is_foremost) + UI().Focus().Update(wnd); + m_b_in_update = false; if (!m_dialogsToRender_new.empty()) { @@ -316,6 +319,33 @@ bool CDialogHolder::IR_UIOnKeyboardPress(int dik) if (TIR->OnKeyboardAction(dik, WINDOW_KEY_PRESSED)) return true; + if (dik > XR_CONTROLLER_BUTTON_INVALID && dik < XR_CONTROLLER_BUTTON_MAX) + { + FocusDirection direction = FocusDirection::Same; + switch (GetBindedAction(dik, EKeyContext::UI)) + { + case kUI_MOVE_LEFT: direction = FocusDirection::Left; break; + case kUI_MOVE_RIGHT: direction = FocusDirection::Right; break; + case kUI_MOVE_UP: direction = FocusDirection::Up; break; + case kUI_MOVE_DOWN: direction = FocusDirection::Down; break; + } + + if (direction != FocusDirection::Same) + { + auto& focus = UI().Focus(); + const auto focused = focus.GetFocused(); + const Fvector2 vec = focused ? focused->GetWndPos() : UI().GetUICursor().GetCursorPosition(); + const auto [target, direct] = focus.FindClosestFocusable(vec, direction); + + if (target) + { + focus.SetFocused(target); + GetUICursor().WarpToWindow(target, true); + return true; + } + } + } + if (!TIR->StopAnyMove() && g_pGameLevel) { IGameObject* O = Level().CurrentEntity(); @@ -334,24 +364,6 @@ bool CDialogHolder::IR_UIOnKeyboardPress(int dik) } } - /*if (const auto focused = GetFocused()) - { - CUIWindow* target{}; - switch (GetBindedAction(dik, EKeyContext::UI)) - { - case kUI_MOVE_LEFT: target = FindClosestFocusable(focused, FocusDirection::Left); break; - case kUI_MOVE_RIGHT: target = FindClosestFocusable(focused, FocusDirection::Right); break; - case kUI_MOVE_UP: target = FindClosestFocusable(focused, FocusDirection::Up); break; - case kUI_MOVE_DOWN: target = FindClosestFocusable(focused, FocusDirection::Down); break; - } - - if (target) - { - SetFocused(target); - GetUICursor().WarpToWindow(target, true); - } - }*/ - return true; } @@ -574,6 +586,7 @@ bool CDialogHolder::IR_UIOnControllerHold(int dik, float x, float y) bool CDialogHolder::FillDebugTree(const CUIDebugState& debugState) { +#ifndef MASTER_GOLD // XXX: Was this meant to be used somewhere here? Because currently its unused and could also be constexpr //ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_OpenOnArrow; @@ -600,6 +613,7 @@ bool CDialogHolder::FillDebugTree(const CUIDebugState& debugState) ImGui::TreePop(); } } +#endif return true; } diff --git a/src/xrGame/UIDialogHolder.h b/src/xrGame/UIDialogHolder.h index b6c5d58c015..788c8bd799f 100644 --- a/src/xrGame/UIDialogHolder.h +++ b/src/xrGame/UIDialogHolder.h @@ -4,7 +4,6 @@ #include "xrCore/_flags.h" #include "xrEngine/pure.h" #include "xrUICore/ui_debug.h" -#include "xrUICore/ui_focus.h" #include @@ -33,7 +32,7 @@ class recvItem Flags8 m_flags; }; -class CDialogHolder : public pureFrame, public CUIDebuggable, public CUIFocusSystem +class CDialogHolder : public pureFrame, public CUIDebuggable { // dialogs xr_vector m_input_receivers; diff --git a/src/xrGame/ui/UIDialogWnd.h b/src/xrGame/ui/UIDialogWnd.h index cb448b7ce6a..97a97cd5d92 100644 --- a/src/xrGame/ui/UIDialogWnd.h +++ b/src/xrGame/ui/UIDialogWnd.h @@ -22,15 +22,7 @@ class CUIDialogWnd : public CUIWindow bool OnControllerAction(int axis, float x, float y, EUIMessages controller_action) override; CDialogHolder* GetHolder() const { return m_pParentHolder; } - - void SetHolder(CDialogHolder* h) - { - if (m_pParentHolder) - m_pParentHolder->UnregisterFocusable(this); - m_pParentHolder = h; - } - - CUIFocusSystem* GetCurrentFocusSystem() const override { return GetHolder(); } + void SetHolder(CDialogHolder* h) { m_pParentHolder = h; } virtual bool StopAnyMove() { return true; } virtual bool NeedCursor() const { return pInput->IsCurrentInputTypeKeyboardMouse(); } diff --git a/src/xrUICore/Buttons/UIButton.cpp b/src/xrUICore/Buttons/UIButton.cpp index d88a60f578d..4a667638465 100644 --- a/src/xrUICore/Buttons/UIButton.cpp +++ b/src/xrUICore/Buttons/UIButton.cpp @@ -10,7 +10,6 @@ CUIButton::CUIButton() : CUIStatic("CUIButton") { - m_bFocusValuable = true; m_eButtonState = BUTTON_NORMAL; m_bIsSwitch = false; @@ -22,6 +21,13 @@ CUIButton::CUIButton() : CUIStatic("CUIButton") TextItemControl()->SetTextComplexMode(false); TextItemControl()->SetTextAlignment(CGameFont::alCenter); // this will create class instance for m_pLines TextItemControl()->SetVTextAlignment(valCenter); + + UI().Focus().RegisterFocusable(this); +} + +CUIButton::~CUIButton() +{ + UI().Focus().UnregisterFocusable(this); } void CUIButton::Reset() diff --git a/src/xrUICore/Buttons/UIButton.h b/src/xrUICore/Buttons/UIButton.h index f38a12e5f0c..c878133eb3d 100644 --- a/src/xrUICore/Buttons/UIButton.h +++ b/src/xrUICore/Buttons/UIButton.h @@ -8,6 +8,7 @@ class XRUICORE_API CUIButton : public CUIStatic public: CUIButton(); + ~CUIButton() override; virtual bool OnMouseAction(float x, float y, EUIMessages mouse_action); virtual void OnClick(); diff --git a/src/xrUICore/ComboBox/UIComboBox.cpp b/src/xrUICore/ComboBox/UIComboBox.cpp index 72e7a2bf6e0..0eb36acda94 100644 --- a/src/xrUICore/ComboBox/UIComboBox.cpp +++ b/src/xrUICore/ComboBox/UIComboBox.cpp @@ -9,8 +9,6 @@ CUIComboBox::CUIComboBox() : CUIWindow("CUIComboBox") { - m_bFocusValuable = true; - AttachChild(&m_frameLine); AttachChild(&m_text); @@ -21,6 +19,13 @@ CUIComboBox::CUIComboBox() : CUIWindow("CUIComboBox") m_bInited = false; m_eState = LIST_FONDED; m_textColor[0] = 0xff00ff00; + + UI().Focus().RegisterFocusable(this); +} + +CUIComboBox::~CUIComboBox() +{ + UI().Focus().UnregisterFocusable(this); } void CUIComboBox::SetListLength(int length) diff --git a/src/xrUICore/ComboBox/UIComboBox.h b/src/xrUICore/ComboBox/UIComboBox.h index db1f4847b6c..7d6c67aa379 100644 --- a/src/xrUICore/ComboBox/UIComboBox.h +++ b/src/xrUICore/ComboBox/UIComboBox.h @@ -16,6 +16,7 @@ class XRUICORE_API CUIComboBox final : public CUIWindow, public CUIOptionsItem, public: CUIComboBox(); + ~CUIComboBox() override; // CUIOptionsItem virtual void SetCurrentOptValue(); // opt->current diff --git a/src/xrUICore/EditBox/UICustomEdit.cpp b/src/xrUICore/EditBox/UICustomEdit.cpp index 03f7ea3e5e4..bab1dfdf6ec 100644 --- a/src/xrUICore/EditBox/UICustomEdit.cpp +++ b/src/xrUICore/EditBox/UICustomEdit.cpp @@ -8,8 +8,6 @@ CUICustomEdit::CUICustomEdit() : CUIStatic("CUICustomEdit") { - m_bFocusValuable = true; - m_editor_control = xr_new(EDIT_BUF_SIZE); Init(EDIT_BUF_SIZE); @@ -25,9 +23,15 @@ CUICustomEdit::CUICustomEdit() : CUIStatic("CUICustomEdit") m_force_update = true; m_last_key_state_time = 0; m_next_focus_capturer = NULL; + + UI().Focus().RegisterFocusable(this); } -CUICustomEdit::~CUICustomEdit() { xr_delete(m_editor_control); } +CUICustomEdit::~CUICustomEdit() +{ + xr_delete(m_editor_control); + UI().Focus().UnregisterFocusable(this); +} text_editor::line_edit_control& CUICustomEdit::ec() { diff --git a/src/xrUICore/TrackBar/UITrackBar.cpp b/src/xrUICore/TrackBar/UITrackBar.cpp index 559c37cdaab..fabe82ec9f1 100644 --- a/src/xrUICore/TrackBar/UITrackBar.cpp +++ b/src/xrUICore/TrackBar/UITrackBar.cpp @@ -19,8 +19,6 @@ CUITrackBar::CUITrackBar() : m_b_invert(false), m_b_is_float(true), m_b_bound_already_set(false), m_f_val(0), m_f_max(1), m_f_min(0), m_f_step(0.01f), m_f_opt_backup_value(0) { - m_bFocusValuable = true; - m_pSlider = xr_new(); AttachChild(m_pSlider); m_pSlider->SetAutoDelete(true); @@ -31,6 +29,13 @@ CUITrackBar::CUITrackBar() m_static->SetAutoDelete(true); m_b_mouse_capturer = false; + + //UI().Focus().RegisterFocusable(this); +} + +CUITrackBar::~CUITrackBar() +{ + //UI().Focus().UnregisterFocusable(this); } bool CUITrackBar::OnMouseAction(float x, float y, EUIMessages mouse_action) diff --git a/src/xrUICore/TrackBar/UITrackBar.h b/src/xrUICore/TrackBar/UITrackBar.h index 289ff9b36bb..6bad3dfe146 100644 --- a/src/xrUICore/TrackBar/UITrackBar.h +++ b/src/xrUICore/TrackBar/UITrackBar.h @@ -8,6 +8,8 @@ class XRUICORE_API CUITrackBar final : public CUI_IB_FrameLineWnd, public CUIOpt { public: CUITrackBar(); + ~CUITrackBar() override; + // CUIOptionsItem virtual void SetCurrentOptValue(); // opt->current virtual void SaveBackUpOptValue(); // current->backup diff --git a/src/xrUICore/Windows/UIWindow.cpp b/src/xrUICore/Windows/UIWindow.cpp index c940abe8860..1546a4068c9 100644 --- a/src/xrUICore/Windows/UIWindow.cpp +++ b/src/xrUICore/Windows/UIWindow.cpp @@ -2,7 +2,6 @@ #include "UIWindow.h" -#include "ui_focus.h" #include "Cursor/UICursor.h" CUIWindow::CUIWindow(pcstr window_name) : m_windowName(window_name) @@ -43,25 +42,6 @@ void CUIWindow::Draw(float x, float y) void CUIWindow::Update() { - /*if (auto* focusSystem = GetCurrentFocusSystem()) - { - const bool valuable = IsFocusValuable(); - const bool registered = focusSystem->IsRegistered(this); - if (valuable) - { - if (!registered) - focusSystem->RegisterFocusable(this); - if (!focusSystem->GetFocused()) - focusSystem->SetFocused(this); - } - else if (!valuable && registered) - { - if (focusSystem->GetFocused() == this) - focusSystem->SetFocused(nullptr); - focusSystem->UnregisterFocusable(this); - } - }*/ - bool cursor_on_window = false; if (GetUICursor().IsVisible()) { @@ -572,10 +552,13 @@ bool CUIWindow::FillDebugTree(const CUIDebugState& debugState) rnd.seed((s32)(intptr_t)this); color = color_rgba(rnd.randI(255), rnd.randI(255), rnd.randI(255), 255); } - else if (GetCurrentFocusSystem() && GetCurrentFocusSystem()->GetFocused() == this) - color = color_rgba(200, 150, 200, 255); - else if (IsFocusValuable()) - color = color_rgba(255, 0, 255, 255); + else if (IsFocusValuable(nullptr)) + { + if (CursorOverWindow()) + color = color_rgba(200, 150, 200, 255); + else + color = color_rgba(255, 0, 255, 255); + } const auto draw_list = hovered ? ImGui::GetForegroundDrawList() : ImGui::GetBackgroundDrawList(); draw_list->AddRect((const ImVec2&)rect.lt, (const ImVec2&)rect.rb, color); diff --git a/src/xrUICore/Windows/UIWindow.h b/src/xrUICore/Windows/UIWindow.h index bb8f8127450..09940ecb1e1 100644 --- a/src/xrUICore/Windows/UIWindow.h +++ b/src/xrUICore/Windows/UIWindow.h @@ -12,8 +12,6 @@ #include "xrUICore/uiabstract.h" #include "xrUICore/ui_debug.h" -class CUIFocusSystem; - class XRUICORE_API CUIWindow : public CUISimpleWindow, public CUIDebuggable { public: @@ -83,24 +81,22 @@ class XRUICORE_API CUIWindow : public CUISimpleWindow, public CUIDebuggable virtual void Enable(bool status) { m_bIsEnabled = status; } - void SetFocusValuable(bool valuable) { m_bFocusValuable = valuable; } - [[nodiscard]] bool IsEnabled() const { return m_bIsEnabled; } [[nodiscard]] - bool IsFocusValuable() const + bool IsFocusValuable(const CUIWindow* parent) const { - if (!m_bFocusValuable) - return false; - bool ok; - for (auto it = this; ; it = it->GetParent()) + const CUIWindow* it = this; + for (;; it = it->GetParent()) { ok = it->IsShown() && it->IsEnabled(); if (!ok || !it->GetParent()) break; } + if (parent && parent != it) + return false; return ok; } @@ -111,13 +107,6 @@ class XRUICORE_API CUIWindow : public CUISimpleWindow, public CUIDebuggable Enable(status); } - virtual CUIFocusSystem* GetCurrentFocusSystem() const - { - if (m_pParentWnd) - return m_pParentWnd->GetCurrentFocusSystem(); - return nullptr; - } - [[nodiscard]] virtual bool IsShown() const { return GetVisible(); } @@ -167,7 +156,10 @@ class XRUICORE_API CUIWindow : public CUISimpleWindow, public CUIDebuggable using WINDOW_LIST = ui_list; + [[nodiscard]] WINDOW_LIST& GetChildWndList() { return m_ChildWndList; } + [[nodiscard]] + const WINDOW_LIST& GetChildWndList() const { return m_ChildWndList; } [[nodiscard]] IC bool IsAutoDelete() const { return m_bAutoDelete; } @@ -237,8 +229,6 @@ class XRUICORE_API CUIWindow : public CUISimpleWindow, public CUIDebuggable // Если курсор над окном bool m_bCursorOverWindow{}; bool m_bCustomDraw{}; - - bool m_bFocusValuable{}; }; XRUICORE_API bool fit_in_rect(CUIWindow* w, Frect const& vis_rect, float border = 0.0f, float dx16pos = 0.0f); diff --git a/src/xrUICore/Windows/UIWindow_script.cpp b/src/xrUICore/Windows/UIWindow_script.cpp index 4dbc2407b24..34303a1aa82 100644 --- a/src/xrUICore/Windows/UIWindow_script.cpp +++ b/src/xrUICore/Windows/UIWindow_script.cpp @@ -9,7 +9,6 @@ #include "ScrollView/UIScrollView.h" #include "Hint/UIHint.h" #include "Cursor/UICursor.h" -#include "ui_focus.h" #include "ui_styles.h" #include "xrScriptEngine/ScriptExporter.hpp" @@ -90,11 +89,17 @@ SCRIPT_EXPORT(CUIFocusSystem, (), value("LowerLeft", (int)FocusDirection::LowerLeft), value("LowerRight", (int)FocusDirection::LowerRight) ], + class_("CUIFocusSystem") .def("RegisterFocusable", &CUIFocusSystem::RegisterFocusable) .def("UnregisterFocusable", &CUIFocusSystem::UnregisterFocusable) .def("IsRegistered", &CUIFocusSystem::IsRegistered) - .def("FindClosestFocusable", &CUIFocusSystem::FindClosestFocusable) + .def("Update", &CUIFocusSystem::Update) + .def("GetFocused", &CUIFocusSystem::GetFocused) + .def("SetFocused", &CUIFocusSystem::SetFocused) + .def("FindClosestFocusable", &CUIFocusSystem::FindClosestFocusable), + + def("GetUIFocusSystem", +[] { return UI().Focus(); }) ]; }); @@ -214,8 +219,6 @@ SCRIPT_EXPORT(CUIWindow, (), .def("SetFont", &CUIWindow::SetFont) .def("GetFont", &CUIWindow::GetFont) - .def("GetCurrentFocusSystem", &CUIWindow::GetCurrentFocusSystem) - .def("WindowName", +[](CUIWindow* self) -> pcstr { return self->WindowName().c_str(); }) .def("SetWindowName", &CUIWindow::SetWindowName), diff --git a/src/xrUICore/ui_base.h b/src/xrUICore/ui_base.h index b8c1d758bc3..6bde2d9d98c 100644 --- a/src/xrUICore/ui_base.h +++ b/src/xrUICore/ui_base.h @@ -2,6 +2,7 @@ #include "ui_defs.h" #include "ui_debug.h" +#include "ui_focus.h" #include "FontManager/FontManager.h" #include "xrEngine/pure.h" @@ -23,6 +24,7 @@ class XRUICORE_API UICore : public CDeviceResetNotifier, public CUIResetNotifier CFontManager* m_pFontManager; CUICursor* m_pUICursor; + CUIFocusSystem m_focusSystem; CUIDebugger m_debugger; Fvector2 m_pp_scale_; @@ -37,6 +39,7 @@ class XRUICORE_API UICore : public CDeviceResetNotifier, public CUIResetNotifier void ReadTextureInfo(); CFontManager& Font() { return *m_pFontManager; } CUICursor& GetUICursor() { return *m_pUICursor; } + auto& Focus() { return m_focusSystem; } auto& Debugger() { return m_debugger; } IC float ClientToScreenScaledX(float left) const { return left * m_current_scale->x; }; IC float ClientToScreenScaledY(float top) const { return top * m_current_scale->y; }; diff --git a/src/xrUICore/ui_focus.cpp b/src/xrUICore/ui_focus.cpp index cafc2f087c9..3a39d38cb73 100644 --- a/src/xrUICore/ui_focus.cpp +++ b/src/xrUICore/ui_focus.cpp @@ -19,137 +19,193 @@ limitations under the License. #include "ui_focus.h" #include "Windows/UIWindow.h" -#include +#include "xrCore/buffer_vector.h" namespace { -float euclidean_distance(const CUIWindow* from, const CUIWindow* to) +std::array allowed_directions(FocusDirection direction) { - const auto& [aX, aY] = from->GetWndPos(); - const auto& [bX, bY] = to->GetWndPos(); + switch (direction) + { + case FocusDirection::Up: + return { FocusDirection::UpperLeft, FocusDirection::Up, FocusDirection::UpperRight }; + + default: + case FocusDirection::Same: + // Normally, you should not request same direction. + VERIFY(false); + + // Down would be the most natural default direction + // With the semantical meaning "enter into something" + [[fallthrough]]; + + case FocusDirection::Down: + return { FocusDirection::LowerLeft, FocusDirection::Down, FocusDirection::LowerRight }; + + case FocusDirection::Left: + return { FocusDirection::UpperLeft, FocusDirection::Left, FocusDirection::LowerLeft }; + + case FocusDirection::Right: + return { FocusDirection::UpperRight, FocusDirection::Right, FocusDirection::LowerRight }; - const auto& [aWidth, aHeight] = from->GetWndSize(); - const auto& [bWidth, bHeight] = to->GetWndSize(); + case FocusDirection::UpperLeft: + return { FocusDirection::Up, FocusDirection::UpperLeft, FocusDirection::Left }; - const Fvector2 centerA = { aX + aWidth / 2.0f, aY + aHeight / 2.0f }; - const Fvector2 centerB = { bX + bWidth / 2.0f, bY + bHeight / 2.0f }; + case FocusDirection::UpperRight: + return { FocusDirection::Up, FocusDirection::UpperRight, FocusDirection::Right }; - return sqrtf(powf(centerB.x - centerA.x, 2) + powf(centerB.y - centerA.y, 2)); + case FocusDirection::LowerLeft: + return { FocusDirection::Left, FocusDirection::LowerLeft, FocusDirection::Down }; + + case FocusDirection::LowerRight: + return { FocusDirection::Right, FocusDirection::LowerRight, FocusDirection::Down }; + } // switch (direction) } + +float get_distance(const Fvector2& a, const Fvector2& b) +{ + Fvector2 c; + c.sub(b, a); + return c.dotproduct(c); } -CUIFocusSystem::FocusData CUIFocusSystem::CalculateFocusData(const CUIWindow* from, const CUIWindow* to) +FocusDirection get_focus_direction(const Fvector2& a, const Fvector2& b) { - const auto& fromPos = from->GetWndPos(); - const auto& toPos = to->GetWndPos(); + if (a.similar(b, EPS_S)) + return FocusDirection::Same; - const bool upper = fromPos.y > toPos.y; - const bool lower = fromPos.y < toPos.y; - const bool left = fromPos.x < toPos.x; - const bool right = fromPos.x > toPos.x; + Fvector2 delta; + delta.sub(b, a); - FocusDirection direction; - if (upper) - { - if (left) - direction = FocusDirection::UpperLeft; - else if (right) - direction = FocusDirection::UpperRight; - else - direction = FocusDirection::Up; - } - else if (lower) + if (fis_zero(delta.y)) // same y { - if (left) - direction = FocusDirection::LowerLeft; - else if (right) - direction = FocusDirection::LowerRight; - else - direction = FocusDirection::Down; - } - else if (left) - { - direction = FocusDirection::Left; - } - else if (right) - { - direction = FocusDirection::Right; + if (delta.x < 0) + return FocusDirection::Left; + + return FocusDirection::Right; } - else + if (delta.y < 0) // 'to' is above { - direction = FocusDirection::Same; + if (fis_zero(delta.x)) // same x + return FocusDirection::Up; + if (delta.x < 0) + return FocusDirection::UpperLeft; + + return FocusDirection::UpperRight; } - return - { - euclidean_distance(from, to), - direction - }; + // 'to' is below + if (fis_zero(delta.x)) // same x + return FocusDirection::Down; + if (delta.x < 0) + return FocusDirection::LowerLeft; + + return FocusDirection::LowerRight; } +} // namespace -void CUIFocusSystem::RegisterFocusable(CUIWindow* focusable) +void CUIFocusSystem::RegisterFocusable(const CUIWindow* focusable) { - ZoneScoped; - - auto& my_relates = m_structure[focusable]; - - // Split calculations to make it more CPU-cache friendly: - // 1. Calculate relations from focusable to all windows - for (auto& window : m_all_windows) - my_relates[window] = CalculateFocusData(focusable, window); + if (!focusable || IsRegistered(focusable)) + return; - // 2. Calculate relations from all windows to focusable - for (auto& window : m_all_windows) - m_structure[window][focusable] = CalculateFocusData(window, focusable); - - m_all_windows.emplace_back(focusable); + m_non_valuable.emplace_back(focusable); } -void CUIFocusSystem::UnregisterFocusable(CUIWindow* focusable) +void CUIFocusSystem::UnregisterFocusable(const CUIWindow* focusable) { - if (const auto it = m_structure.find(focusable); - it != m_structure.end()) + if (!focusable) + return; + + if (const auto it = std::find(m_valuable.begin(), m_valuable.end(), focusable); + it != m_valuable.end()) { - it->second.clear(); - m_structure.erase(it); + m_valuable.erase(it); } - if (const auto it = std::find(m_all_windows.begin(), m_all_windows.end(), focusable); - it != m_all_windows.end()) + if (const auto it = std::find(m_non_valuable.begin(), m_non_valuable.end(), focusable); + it != m_non_valuable.end()) { - m_all_windows.erase(it); + m_non_valuable.erase(it); } } bool CUIFocusSystem::IsRegistered(const CUIWindow* focusable) const { - const auto& it = m_structure.find(const_cast(focusable)); - return it != m_structure.end(); + if (!focusable) + return false; + + const auto it = std::find(m_valuable.begin(), m_valuable.end(), focusable); + const auto it2 = std::find(m_non_valuable.begin(), m_non_valuable.end(), focusable); + + return it != m_valuable.end() || it2 != m_non_valuable.end(); } -CUIWindow* CUIFocusSystem::FindClosestFocusable(CUIWindow* target, FocusDirection direction) const +void CUIFocusSystem::Update(const CUIWindow* root) { - const auto& it = m_structure.find(target); - if (it == m_structure.end()) + // temp vector allows to prevent calling for IsFocusValuable twice. + buffer_vector temp{ xr_alloca(sizeof(CUIWindow*) * m_valuable.size()), m_valuable.size() }; + + for (auto it = m_valuable.begin(); it != m_valuable.end(); ++it) { - VERIFY2(false, "Target CUIWindow is not registered in the focus system."); - return nullptr; + if ((*it)->IsFocusValuable(root)) + continue; + temp.push_back(*it); + it = m_valuable.erase(it); + + if (*it == m_current_focused) + m_current_focused = nullptr; } - const auto& my_relates = it->second; - CUIWindow* closest = nullptr; - float minDistance = type_max; + for (auto it = m_non_valuable.begin(); it != m_non_valuable.end(); ++it) + { + if (!(*it)->IsFocusValuable(root)) + continue; + m_valuable.emplace_back(*it); + it = m_non_valuable.erase(it); + } + + for (const auto window : temp) + m_non_valuable.emplace_back(window); + + // no need to clear temp vector, it's stack allocated. - for (const auto& [window, data] : my_relates) + if (m_current_focused && !m_current_focused->CursorOverWindow()) + m_current_focused = nullptr; +} + +std::pair CUIFocusSystem::FindClosestFocusable(const Fvector2& from, FocusDirection direction) const +{ + const CUIWindow* closest = nullptr; + const CUIWindow* closest2 = nullptr; + float min_distance = type_max; + float min_distance2 = type_max; + + const auto [dir1, mainDir, dir2] = allowed_directions(direction); + + for (const auto& window : m_valuable) { - if (data.distance < minDistance && data.direction == direction) + const auto to_pos = window->GetAbsoluteCenterPos(); + + const auto dist = get_distance(from, to_pos); + const auto dir = get_focus_direction(from, to_pos); + + if (dist < min_distance && dir == mainDir) { - minDistance = data.distance; + min_distance = dist; closest = window; } + if (dist < min_distance2 && (dir == dir1 || dir == dir2)) + { + min_distance2 = dist; + closest2 = window; + } } // We hold const pointers to guarantee that we don't do anything. // But the caller can do anything. - return closest; + if (closest) + return { const_cast(closest), true }; + + return { const_cast(closest2), false }; } diff --git a/src/xrUICore/ui_focus.h b/src/xrUICore/ui_focus.h index 08cb464aa0b..ab7f7d295f3 100644 --- a/src/xrUICore/ui_focus.h +++ b/src/xrUICore/ui_focus.h @@ -18,12 +18,11 @@ limitations under the License. #include "ui_defs.h" -#include "xrCommon/xr_map.h" -#include "xrCommon/xr_unordered_map.h" +#include "xrCommon/xr_list.h" class CUIWindow; -enum class FocusDirection +enum class FocusDirection : u8 { Same, // exactly same coordinates with the target Up, @@ -36,37 +35,25 @@ enum class FocusDirection LowerRight, }; -// 1. Doesn't own CUIWindow* pointers it holds +// Doesn't own CUIWindow* pointers it holds class XRUICORE_API CUIFocusSystem { - struct FocusData - { - float distance; - FocusDirection direction; - }; + xr_list m_valuable; + xr_list m_non_valuable; - using relates_t = xr_map; - using targets_t = xr_unordered_map; - - targets_t m_structure; - - xr_list m_all_windows; // helper to make code simpler - - CUIWindow* m_current_focused{}; - -private: - static FocusData CalculateFocusData(const CUIWindow* from, const CUIWindow* to); + const CUIWindow* m_current_focused{}; public: virtual ~CUIFocusSystem() = default; - void RegisterFocusable(CUIWindow* focusable); - void UnregisterFocusable(CUIWindow* focusable); - + void RegisterFocusable(const CUIWindow* focusable); + void UnregisterFocusable(const CUIWindow* focusable); bool IsRegistered(const CUIWindow* focusable) const; - CUIWindow* FindClosestFocusable(CUIWindow* target, FocusDirection direction) const; + void Update(const CUIWindow* root); + + auto GetFocused() const { return const_cast(m_current_focused);} + void SetFocused(const CUIWindow* window) { m_current_focused = window; } - CUIWindow* GetFocused() const { return m_current_focused;} - void SetFocused(CUIWindow* window) { m_current_focused = window; } + std::pair FindClosestFocusable(const Fvector2& from, FocusDirection direction) const; };