From 1d40687a403b7babdafdd577d10e001840b0f124 Mon Sep 17 00:00:00 2001
From: Herring <ake.svedin@gmail.com>
Date: Mon, 30 Sep 2024 23:18:59 +0200
Subject: [PATCH] Add index to EntryState (#933)

Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
---
 CHANGELOG.md                                  |  4 ++
 include/ftxui/component/component_base.hpp    |  1 +
 include/ftxui/component/component_options.hpp |  1 +
 src/ftxui/component/button.cpp                |  7 +--
 src/ftxui/component/checkbox.cpp              |  5 +--
 src/ftxui/component/component.cpp             | 16 +++++++
 src/ftxui/component/menu.cpp                  | 12 ++---
 src/ftxui/component/menu_test.cpp             | 45 +++++++++++++++++++
 src/ftxui/component/radiobox.cpp              |  5 +--
 9 files changed, 74 insertions(+), 22 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a0afeb2e..611187dc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -35,6 +35,10 @@ current (development)
 - Bugfix: Fix cursor position in when in the last column. See #831.
 - Bugfix: Fix `ResizeableSplit` keyboard navigation. Fixed by #842.
 - Bugfix: Fix `Menu` focus. See #841
+- Feature: Add `ComponentBase::Index()`. This allows to get the index of a
+  component in its parent. See #932
+- Feature: Add `EntryState::index`. This allows to get the index of a menu entry.
+  See #932
 
 ### Dom
 - Feature: Add `hscroll_indicator`. It display an horizontal indicator
diff --git a/include/ftxui/component/component_base.hpp b/include/ftxui/component/component_base.hpp
index 622af0a1..ef1b751c 100644
--- a/include/ftxui/component/component_base.hpp
+++ b/include/ftxui/component/component_base.hpp
@@ -44,6 +44,7 @@ class ComponentBase {
   ComponentBase* Parent() const;
   Component& ChildAt(size_t i);
   size_t ChildCount() const;
+  int Index() const;
   void Add(Component children);
   void Detach();
   void DetachAllChildren();
diff --git a/include/ftxui/component/component_options.hpp b/include/ftxui/component/component_options.hpp
index b7e771e6..a55a4df8 100644
--- a/include/ftxui/component/component_options.hpp
+++ b/include/ftxui/component/component_options.hpp
@@ -25,6 +25,7 @@ struct EntryState {
   bool state;         ///< The state of the button/checkbox/radiobox
   bool active;        ///< Whether the entry is the active one.
   bool focused;       ///< Whether the entry is one focused by the user.
+  int index;          ///< Index of the entry when applicable or -1.
 };
 
 struct UnderlineOption {
diff --git a/src/ftxui/component/button.cpp b/src/ftxui/component/button.cpp
index e2be2afc..844dd396 100644
--- a/src/ftxui/component/button.cpp
+++ b/src/ftxui/component/button.cpp
@@ -48,11 +48,8 @@ class ButtonBase : public ComponentBase, public ButtonOption {
     }
 
     auto focus_management = focused ? focus : active ? select : nothing;
-    const EntryState state = {
-        *label,
-        false,
-        active,
-        focused_or_hover,
+    const EntryState state{
+        *label, false, active, focused_or_hover, Index(),
     };
 
     auto element = (transform ? transform : DefaultTransform)  //
diff --git a/src/ftxui/component/checkbox.cpp b/src/ftxui/component/checkbox.cpp
index c2a10f18..b4b7f158 100644
--- a/src/ftxui/component/checkbox.cpp
+++ b/src/ftxui/component/checkbox.cpp
@@ -28,10 +28,7 @@ class CheckboxBase : public ComponentBase, public CheckboxOption {
     const bool is_active = Active();
     auto focus_management = is_focused ? focus : is_active ? select : nothing;
     auto entry_state = EntryState{
-        *label,
-        *checked,
-        is_active,
-        is_focused || hovered_,
+        *label, *checked, is_active, is_focused || hovered_, -1,
     };
     auto element = (transform ? transform : CheckboxOption::Simple().transform)(
         entry_state);
diff --git a/src/ftxui/component/component.cpp b/src/ftxui/component/component.cpp
index 6ed4cb26..e0c38dd4 100644
--- a/src/ftxui/component/component.cpp
+++ b/src/ftxui/component/component.cpp
@@ -51,6 +51,22 @@ size_t ComponentBase::ChildCount() const {
   return children_.size();
 }
 
+/// @brief Return index of the component in its parent. -1 if no parent.
+/// @ingroup component
+int ComponentBase::Index() const {
+  if (parent_ == nullptr) {
+    return -1;
+  }
+  int index = 0;
+  for (const Component& child : parent_->children_) {
+    if (child.get() == this) {
+      return index;
+    }
+    index++;
+  }
+  return -1;  // Not reached.
+}
+
 /// @brief Add a child.
 /// @@param child The child to be attached.
 /// @ingroup component
diff --git a/src/ftxui/component/menu.cpp b/src/ftxui/component/menu.cpp
index 4fc4d0b9..0f8b0fc4 100644
--- a/src/ftxui/component/menu.cpp
+++ b/src/ftxui/component/menu.cpp
@@ -123,10 +123,7 @@ class MenuBase : public ComponentBase, public MenuOption {
       const bool is_selected = (selected() == i);
 
       const EntryState state = {
-          entries[i],
-          false,
-          is_selected,
-          is_focused,
+          entries[i], false, is_selected, is_focused, i,
       };
 
       auto focus_management = (selected_focus_ != i) ? nothing
@@ -625,11 +622,8 @@ Component MenuEntry(MenuEntryOption option) {
       const bool focused = Focused();
       UpdateAnimationTarget();
 
-      const EntryState state = {
-          label(),
-          false,
-          hovered_,
-          focused,
+      const EntryState state{
+          label(), false, hovered_, focused, Index(),
       };
 
       const Element element =
diff --git a/src/ftxui/component/menu_test.cpp b/src/ftxui/component/menu_test.cpp
index b5465eb9..ca099468 100644
--- a/src/ftxui/component/menu_test.cpp
+++ b/src/ftxui/component/menu_test.cpp
@@ -226,5 +226,50 @@ TEST(MenuTest, AnimationsVertical) {
   }
 }
 
+TEST(MenuTest, EntryIndex) {
+  int selected = 0;
+  std::vector<std::string> entries = {"0", "1", "2"};
+
+  auto option = MenuOption::Vertical();
+  option.entries = &entries;
+  option.selected = &selected;
+  option.entries_option.transform = [&](const EntryState& state) {
+    int curidx = std::stoi(state.label);
+    EXPECT_EQ(state.index, curidx);
+    return text(state.label);
+  };
+  auto menu = Menu(option);
+  menu->OnEvent(Event::ArrowDown);
+  menu->OnEvent(Event::ArrowDown);
+  menu->OnEvent(Event::Return);
+  entries.resize(2);
+  (void)menu->Render();
+}
+
+TEST(MenuTest, MenuEntryIndex) {
+  int selected = 0;
+
+  MenuEntryOption option;
+  option.transform = [&](const EntryState& state) {
+    int curidx = std::stoi(state.label);
+    EXPECT_EQ(state.index, curidx);
+    return text(state.label);
+  };
+  auto menu = Container::Vertical(
+      {
+          MenuEntry("0", option),
+          MenuEntry("1", option),
+          MenuEntry("2", option),
+      },
+      &selected);
+
+  menu->OnEvent(Event::ArrowDown);
+  menu->OnEvent(Event::ArrowDown);
+  menu->OnEvent(Event::Return);
+  for (int index = 0; index < menu->ChildCount(); index++) {
+    EXPECT_EQ(menu->ChildAt(index)->Index(), index);
+  }
+}
+
 }  // namespace ftxui
 // NOLINTEND
diff --git a/src/ftxui/component/radiobox.cpp b/src/ftxui/component/radiobox.cpp
index bb775000..4c823ded 100644
--- a/src/ftxui/component/radiobox.cpp
+++ b/src/ftxui/component/radiobox.cpp
@@ -40,10 +40,7 @@ class RadioboxBase : public ComponentBase, public RadioboxOption {
                               : is_menu_focused ? focus
                                                 : select;
       auto state = EntryState{
-          entries[i],
-          selected() == i,
-          is_selected,
-          is_focused,
+          entries[i], selected() == i, is_selected, is_focused, i,
       };
       auto element =
           (transform ? transform : RadioboxOption::Simple().transform)(state);
-- 
GitLab