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