mirror of
				https://github.com/ArthurSonzogni/FTXUI.git
				synced 2025-10-31 18:48:11 +08:00 
			
		
		
		
	Add non focusable components. (#172)
This commit is contained in:
		| @@ -51,6 +51,11 @@ class ComponentBase { | |||||||
|   // root component contains this object. |   // root component contains this object. | ||||||
|   virtual Component ActiveChild(); |   virtual Component ActiveChild(); | ||||||
|  |  | ||||||
|  |   // Return true when the component contains focusable elements. | ||||||
|  |   // The non focusable Component will be skipped when navigating using the | ||||||
|  |   // keyboard. | ||||||
|  |   virtual bool Focusable() const; | ||||||
|  |  | ||||||
|   // Whether this is the active child of its parent. |   // Whether this is the active child of its parent. | ||||||
|   bool Active() const; |   bool Active() const; | ||||||
|   // Whether all the ancestors are active. |   // Whether all the ancestors are active. | ||||||
|   | |||||||
| @@ -53,6 +53,10 @@ class ButtonBase : public ComponentBase { | |||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   bool Focusable() const final { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   ConstStringRef label_; |   ConstStringRef label_; | ||||||
|   std::function<void()> on_click_; |   std::function<void()> on_click_; | ||||||
|   | |||||||
| @@ -71,6 +71,10 @@ class CheckboxBase : public ComponentBase { | |||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   bool Focusable() const final { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   ConstStringRef label_; |   ConstStringRef label_; | ||||||
|   bool* const state_; |   bool* const state_; | ||||||
|   Box box_; |   Box box_; | ||||||
|   | |||||||
| @@ -106,6 +106,18 @@ Component ComponentBase::ActiveChild() { | |||||||
|   return children_.empty() ? nullptr : children_.front(); |   return children_.empty() ? nullptr : children_.front(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /// @brief Return true when the component contains focusable elements. | ||||||
|  | /// The non focusable Components will be skipped when navigating using the | ||||||
|  | /// keyboard. | ||||||
|  | /// @ingroup component | ||||||
|  | bool ComponentBase::Focusable() const { | ||||||
|  |   for (const Component& child : children_) { | ||||||
|  |     if (child->Focusable()) | ||||||
|  |       return true; | ||||||
|  |   } | ||||||
|  |   return false; | ||||||
|  | } | ||||||
|  |  | ||||||
| /// @brief Returns if the element if the currently active child of its parent. | /// @brief Returns if the element if the currently active child of its parent. | ||||||
| /// @ingroup component | /// @ingroup component | ||||||
| bool ComponentBase::Active() const { | bool ComponentBase::Active() const { | ||||||
|   | |||||||
| @@ -63,6 +63,25 @@ class ContainerBase : public ComponentBase { | |||||||
|  |  | ||||||
|   int selected_ = 0; |   int selected_ = 0; | ||||||
|   int* selector_ = nullptr; |   int* selector_ = nullptr; | ||||||
|  |  | ||||||
|  |   void MoveSelector(int dir) { | ||||||
|  |     for (int i = *selector_ + dir; i >= 0 && i < (int)children_.size(); | ||||||
|  |          i += dir) { | ||||||
|  |       if (children_[i]->Focusable()) { | ||||||
|  |         *selector_ = i; | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   void MoveSelectorWrap(int dir) { | ||||||
|  |     for (size_t offset = 1; offset < children_.size(); ++offset) { | ||||||
|  |       int i = (*selector_ + offset * dir + children_.size()) % children_.size(); | ||||||
|  |       if (children_[i]->Focusable()) { | ||||||
|  |         *selector_ = i; | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class VerticalContainer : public ContainerBase { | class VerticalContainer : public ContainerBase { | ||||||
| @@ -81,13 +100,13 @@ class VerticalContainer : public ContainerBase { | |||||||
|   bool EventHandler(Event event) override { |   bool EventHandler(Event event) override { | ||||||
|     int old_selected = *selector_; |     int old_selected = *selector_; | ||||||
|     if (event == Event::ArrowUp || event == Event::Character('k')) |     if (event == Event::ArrowUp || event == Event::Character('k')) | ||||||
|       (*selector_)--; |       MoveSelector(-1); | ||||||
|     if (event == Event::ArrowDown || event == Event::Character('j')) |     if (event == Event::ArrowDown || event == Event::Character('j')) | ||||||
|       (*selector_)++; |       MoveSelector(+1); | ||||||
|     if (event == Event::Tab && children_.size()) |     if (event == Event::Tab && children_.size()) | ||||||
|       *selector_ = (*selector_ + 1) % children_.size(); |       MoveSelectorWrap(+1); | ||||||
|     if (event == Event::TabReverse && children_.size()) |     if (event == Event::TabReverse && children_.size()) | ||||||
|       *selector_ = (*selector_ + children_.size() - 1) % children_.size(); |       MoveSelectorWrap(-1); | ||||||
|  |  | ||||||
|     *selector_ = std::max(0, std::min(int(children_.size()) - 1, *selector_)); |     *selector_ = std::max(0, std::min(int(children_.size()) - 1, *selector_)); | ||||||
|     return old_selected != *selector_; |     return old_selected != *selector_; | ||||||
| @@ -110,13 +129,13 @@ class HorizontalContainer : public ContainerBase { | |||||||
|   bool EventHandler(Event event) override { |   bool EventHandler(Event event) override { | ||||||
|     int old_selected = *selector_; |     int old_selected = *selector_; | ||||||
|     if (event == Event::ArrowLeft || event == Event::Character('h')) |     if (event == Event::ArrowLeft || event == Event::Character('h')) | ||||||
|       (*selector_)--; |       MoveSelector(-1); | ||||||
|     if (event == Event::ArrowRight || event == Event::Character('l')) |     if (event == Event::ArrowRight || event == Event::Character('l')) | ||||||
|       (*selector_)++; |       MoveSelector(+1); | ||||||
|     if (event == Event::Tab && children_.size()) |     if (event == Event::Tab && children_.size()) | ||||||
|       *selector_ = (*selector_ + 1) % children_.size(); |       MoveSelectorWrap(+1); | ||||||
|     if (event == Event::TabReverse && children_.size()) |     if (event == Event::TabReverse && children_.size()) | ||||||
|       *selector_ = (*selector_ + children_.size() - 1) % children_.size(); |       MoveSelectorWrap(-1); | ||||||
|  |  | ||||||
|     *selector_ = std::max(0, std::min(int(children_.size()) - 1, *selector_)); |     *selector_ = std::max(0, std::min(int(children_.size()) - 1, *selector_)); | ||||||
|     return old_selected != *selector_; |     return old_selected != *selector_; | ||||||
|   | |||||||
| @@ -10,14 +10,24 @@ | |||||||
|  |  | ||||||
| using namespace ftxui; | using namespace ftxui; | ||||||
|  |  | ||||||
|  | Component Focusable() { | ||||||
|  |   return Button(L"", [] {}); | ||||||
|  | } | ||||||
|  | Component NonFocusable() { | ||||||
|  |   return Container::Horizontal({}); | ||||||
|  | } | ||||||
|  |  | ||||||
| TEST(ContainerTest, HorizontalEvent) { | TEST(ContainerTest, HorizontalEvent) { | ||||||
|   auto container = Container::Horizontal({}); |   auto container = Container::Horizontal({}); | ||||||
|   auto c0 = Container::Horizontal({}); |   auto c0 = Focusable(); | ||||||
|   auto c1 = Container::Horizontal({}); |   auto c1 = Focusable(); | ||||||
|   auto c2 = Container::Horizontal({}); |   auto c2 = Focusable(); | ||||||
|   container->Add(c0); |   container->Add(c0); | ||||||
|   container->Add(c1); |   container->Add(c1); | ||||||
|  |   container->Add(NonFocusable()); | ||||||
|  |   container->Add(NonFocusable()); | ||||||
|   container->Add(c2); |   container->Add(c2); | ||||||
|  |   container->Add(NonFocusable()); | ||||||
|  |  | ||||||
|   // With arrow key. |   // With arrow key. | ||||||
|   EXPECT_EQ(container->ActiveChild(), c0); |   EXPECT_EQ(container->ActiveChild(), c0); | ||||||
| @@ -85,12 +95,15 @@ TEST(ContainerTest, HorizontalEvent) { | |||||||
|  |  | ||||||
| TEST(ContainerTest, VerticalEvent) { | TEST(ContainerTest, VerticalEvent) { | ||||||
|   auto container = Container::Vertical({}); |   auto container = Container::Vertical({}); | ||||||
|   auto c0 = Container::Horizontal({}); |   auto c0 = Focusable(); | ||||||
|   auto c1 = Container::Horizontal({}); |   auto c1 = Focusable(); | ||||||
|   auto c2 = Container::Horizontal({}); |   auto c2 = Focusable(); | ||||||
|   container->Add(c0); |   container->Add(c0); | ||||||
|   container->Add(c1); |   container->Add(c1); | ||||||
|  |   container->Add(NonFocusable()); | ||||||
|  |   container->Add(NonFocusable()); | ||||||
|   container->Add(c2); |   container->Add(c2); | ||||||
|  |   container->Add(NonFocusable()); | ||||||
|  |  | ||||||
|   // With arrow key. |   // With arrow key. | ||||||
|   EXPECT_EQ(container->ActiveChild(), c0); |   EXPECT_EQ(container->ActiveChild(), c0); | ||||||
| @@ -158,9 +171,9 @@ TEST(ContainerTest, VerticalEvent) { | |||||||
|  |  | ||||||
| TEST(ContainerTest, SetActiveChild) { | TEST(ContainerTest, SetActiveChild) { | ||||||
|   auto container = Container::Horizontal({}); |   auto container = Container::Horizontal({}); | ||||||
|   auto c0 = Container::Horizontal({}); |   auto c0 = Focusable(); | ||||||
|   auto c1 = Container::Horizontal({}); |   auto c1 = Focusable(); | ||||||
|   auto c2 = Container::Horizontal({}); |   auto c2 = Focusable(); | ||||||
|   container->Add(c0); |   container->Add(c0); | ||||||
|   container->Add(c1); |   container->Add(c1); | ||||||
|   container->Add(c2); |   container->Add(c2); | ||||||
| @@ -215,12 +228,12 @@ TEST(ContainerTest, TakeFocus) { | |||||||
|   auto c1 = Container::Vertical({}); |   auto c1 = Container::Vertical({}); | ||||||
|   auto c2 = Container::Vertical({}); |   auto c2 = Container::Vertical({}); | ||||||
|   auto c3 = Container::Vertical({}); |   auto c3 = Container::Vertical({}); | ||||||
|   auto c11 = Container::Horizontal({}); |   auto c11 = Focusable(); | ||||||
|   auto c12 = Container::Horizontal({}); |   auto c12 = Focusable(); | ||||||
|   auto c13 = Container::Horizontal({}); |   auto c13 = Focusable(); | ||||||
|   auto c21 = Container::Horizontal({}); |   auto c21 = Focusable(); | ||||||
|   auto c22 = Container::Horizontal({}); |   auto c22 = Focusable(); | ||||||
|   auto c23 = Container::Horizontal({}); |   auto c23 = Focusable(); | ||||||
|  |  | ||||||
|   c->Add(c1); |   c->Add(c1); | ||||||
|   c->Add(c2); |   c->Add(c2); | ||||||
|   | |||||||
| @@ -163,6 +163,11 @@ class InputBase : public ComponentBase { | |||||||
|     } |     } | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   bool Focusable() const final { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   StringRef content_; |   StringRef content_; | ||||||
|   ConstStringRef placeholder_; |   ConstStringRef placeholder_; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -106,6 +106,10 @@ class MenuBase : public ComponentBase { | |||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   bool Focusable() const final { | ||||||
|  |     return entries_->size(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   int& focused_entry() { return option_->focused_entry(); } |   int& focused_entry() { return option_->focused_entry(); } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   | |||||||
| @@ -119,6 +119,10 @@ class RadioboxBase : public ComponentBase { | |||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   bool Focusable() const final { | ||||||
|  |     return entries_->size(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   int& focused_entry() { return option_->focused_entry(); } |   int& focused_entry() { return option_->focused_entry(); } | ||||||
|  |  | ||||||
|   const std::vector<std::wstring>* const entries_; |   const std::vector<std::wstring>* const entries_; | ||||||
|   | |||||||
| @@ -84,6 +84,10 @@ class SliderBase : public ComponentBase { | |||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   bool Focusable() const final { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   StringRef label_; |   StringRef label_; | ||||||
|   T* value_; |   T* value_; | ||||||
|   | |||||||
| @@ -107,6 +107,8 @@ class ToggleBase : public ComponentBase { | |||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   bool Focusable() const final { return entries_->size(); } | ||||||
|  |  | ||||||
|   int& focused_entry() { return option_->focused_entry(); } |   int& focused_entry() { return option_->focused_entry(); } | ||||||
|  |  | ||||||
|   const std::vector<std::wstring>* const entries_; |   const std::vector<std::wstring>* const entries_; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Arthur Sonzogni
					Arthur Sonzogni