mirror of
				https://github.com/ArthurSonzogni/FTXUI.git
				synced 2025-11-01 02:58:12 +08:00 
			
		
		
		
	Add TakeFocus and SetActiveChild.
This allows developers to set child children component must be the currently active/focused one. This can be used to "control" where the focus is, without user interactions.
This commit is contained in:
		 ArthurSonzogni
					ArthurSonzogni
				
			
				
					committed by
					
						 Arthur Sonzogni
						Arthur Sonzogni
					
				
			
			
				
	
			
			
			 Arthur Sonzogni
						Arthur Sonzogni
					
				
			
						parent
						
							114ab4ae2a
						
					
				
				
					commit
					81d79d311d
				
			| @@ -9,7 +9,7 @@ execute_process( | ||||
|  | ||||
| project(ftxui | ||||
|   LANGUAGES CXX | ||||
|   VERSION 0.1.${git_version} | ||||
|   VERSION 0.2.${git_version} | ||||
| ) | ||||
|  | ||||
| option(FTXUI_BUILD_EXAMPLES "Set to ON to build examples" ON) | ||||
|   | ||||
| @@ -39,11 +39,18 @@ class Component { | ||||
|   // We say an element has the focus if the chain of ActiveChild() from the | ||||
|   // root component contains this object. | ||||
|   virtual Component* ActiveChild(); | ||||
|  | ||||
|   // Whether this is the active child of its parent. | ||||
|   bool Active(); | ||||
|   // Whether all the ancestors are active. | ||||
|   bool Focused(); | ||||
|  | ||||
|   // Make the |child| to be the "active" one. | ||||
|   virtual void SetActiveChild(Component* child); | ||||
|  | ||||
|   // Configure all the ancestors to give focus to this component. | ||||
|   void TakeFocus(); | ||||
|  | ||||
|  private: | ||||
|   Component* parent_ = nullptr; | ||||
|   void Detach(); | ||||
|   | ||||
| @@ -18,6 +18,7 @@ class Container : public Component { | ||||
|   bool OnEvent(Event event) override; | ||||
|   Element Render() override; | ||||
|   Component* ActiveChild() override; | ||||
|   virtual void SetActiveChild(Component*) override; | ||||
|  | ||||
|  protected: | ||||
|   // Handlers | ||||
|   | ||||
| @@ -58,9 +58,16 @@ Component* Component::ActiveChild() { | ||||
|   return children_.empty() ? nullptr : children_.front(); | ||||
| } | ||||
|  | ||||
| /// @brief Returns if the element if the currently active child of its parent. | ||||
| /// @ingroup component | ||||
| bool Component::Active() { | ||||
|   return !parent_ || parent_->ActiveChild() == this; | ||||
| } | ||||
|  | ||||
| /// @brief Returns if the elements if focused by the user. | ||||
| /// True when the Component is focused by the user. An element is Focused when | ||||
| /// it is with all its ancestors the ActiveChild() of their parents. | ||||
| /// @ingroup component | ||||
| bool Component::Focused() { | ||||
|   Component* current = this; | ||||
|   for (;;) { | ||||
| @@ -73,6 +80,23 @@ bool Component::Focused() { | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// @brief Make the |child| to be the "active" one. | ||||
| /// @argument child the child to become active. | ||||
| /// @ingroup component | ||||
| void Component::SetActiveChild(Component*) {} | ||||
|  | ||||
| /// @brief Configure all the ancestors to give focus to this component. | ||||
| /// @ingroup component | ||||
| void Component::TakeFocus() { | ||||
|   Component* child = this; | ||||
|   Component* parent = parent_; | ||||
|   while (parent) { | ||||
|     parent->SetActiveChild(child); | ||||
|     child = parent; | ||||
|     parent = parent->parent_; | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// @brief Detach this children from its parent. | ||||
| /// @see Attach | ||||
| /// @see Detach | ||||
|   | ||||
| @@ -47,6 +47,15 @@ Component* Container::ActiveChild() { | ||||
|   return children_[selected % children_.size()]; | ||||
| } | ||||
|  | ||||
| void Container::SetActiveChild(Component* child) { | ||||
|   for(size_t i = 0; i < children_.size(); ++i) { | ||||
|     if (children_[i] == child) { | ||||
|       (selector_ ? *selector_ : selected_) = i; | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| bool Container::VerticalEvent(Event event) { | ||||
|   int old_selected = selected_; | ||||
|   if (event == Event::ArrowUp || event == Event::Character('k')) | ||||
|   | ||||
| @@ -75,6 +75,211 @@ TEST(ContainerTest, HorizontalEvent) { | ||||
|   container.OnEvent(Event::TabReverse); | ||||
| } | ||||
|  | ||||
| TEST(ContainerTest, VerticalEvent) { | ||||
|   auto container = Container::Vertical(); | ||||
|   Component c0, c1, c2; | ||||
|   container.Add(&c0); | ||||
|   container.Add(&c1); | ||||
|   container.Add(&c2); | ||||
|  | ||||
|   // With arrow key. | ||||
|   EXPECT_EQ(container.ActiveChild(), &c0); | ||||
|   container.OnEvent(Event::ArrowDown); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c1); | ||||
|   container.OnEvent(Event::ArrowDown); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c2); | ||||
|   container.OnEvent(Event::ArrowDown); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c2); | ||||
|   container.OnEvent(Event::ArrowUp); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c1); | ||||
|   container.OnEvent(Event::ArrowUp); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c0); | ||||
|   container.OnEvent(Event::ArrowUp); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c0); | ||||
|  | ||||
|   // With arrow key in the wrong dimension. | ||||
|   container.OnEvent(Event::ArrowLeft); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c0); | ||||
|   container.OnEvent(Event::ArrowRight); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c0); | ||||
|  | ||||
|   // With vim like characters. | ||||
|   EXPECT_EQ(container.ActiveChild(), &c0); | ||||
|   container.OnEvent(Event::Character('j')); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c1); | ||||
|   container.OnEvent(Event::Character('j')); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c2); | ||||
|   container.OnEvent(Event::Character('j')); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c2); | ||||
|   container.OnEvent(Event::Character('k')); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c1); | ||||
|   container.OnEvent(Event::Character('k')); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c0); | ||||
|   container.OnEvent(Event::Character('k')); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c0); | ||||
|  | ||||
|   // With vim like characters in the wrong direction. | ||||
|   container.OnEvent(Event::Character('h')); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c0); | ||||
|   container.OnEvent(Event::Character('l')); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c0); | ||||
|  | ||||
|   // With tab characters. | ||||
|   container.OnEvent(Event::Tab); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c1); | ||||
|   container.OnEvent(Event::Tab); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c2); | ||||
|   container.OnEvent(Event::Tab); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c0); | ||||
|   container.OnEvent(Event::Tab); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c1); | ||||
|   container.OnEvent(Event::Tab); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c2); | ||||
|   container.OnEvent(Event::TabReverse); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c1); | ||||
|   container.OnEvent(Event::TabReverse); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c0); | ||||
|   container.OnEvent(Event::TabReverse); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c2); | ||||
|   container.OnEvent(Event::TabReverse); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c1); | ||||
|   container.OnEvent(Event::TabReverse); | ||||
| } | ||||
|  | ||||
| TEST(ContainerTest, SetActiveChild) { | ||||
|   auto container = Container::Horizontal(); | ||||
|   Component c0, c1, c2; | ||||
|   container.Add(&c0); | ||||
|   container.Add(&c1); | ||||
|   container.Add(&c2); | ||||
|  | ||||
|   EXPECT_EQ(container.ActiveChild(), &c0); | ||||
|   EXPECT_TRUE(c0.Focused()); | ||||
|   EXPECT_TRUE(c0.Active()); | ||||
|   EXPECT_FALSE(c1.Focused()); | ||||
|   EXPECT_FALSE(c1.Active()); | ||||
|   EXPECT_FALSE(c2.Focused()); | ||||
|   EXPECT_FALSE(c2.Active()); | ||||
|  | ||||
|   container.SetActiveChild(&c0); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c0); | ||||
|   EXPECT_TRUE(c0.Focused()); | ||||
|   EXPECT_TRUE(c0.Active()); | ||||
|   EXPECT_FALSE(c1.Focused()); | ||||
|   EXPECT_FALSE(c1.Active()); | ||||
|   EXPECT_FALSE(c2.Focused()); | ||||
|   EXPECT_FALSE(c2.Active()); | ||||
|  | ||||
|   container.SetActiveChild(&c1); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c1); | ||||
|   EXPECT_FALSE(c0.Focused()); | ||||
|   EXPECT_FALSE(c0.Active()); | ||||
|   EXPECT_TRUE(c1.Focused()); | ||||
|   EXPECT_TRUE(c1.Active()); | ||||
|   EXPECT_FALSE(c2.Focused()); | ||||
|   EXPECT_FALSE(c2.Active()); | ||||
|  | ||||
|   container.SetActiveChild(&c2); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c2); | ||||
|   EXPECT_FALSE(c0.Focused()); | ||||
|   EXPECT_FALSE(c0.Active()); | ||||
|   EXPECT_FALSE(c1.Focused()); | ||||
|   EXPECT_FALSE(c1.Active()); | ||||
|   EXPECT_TRUE(c2.Focused()); | ||||
|   EXPECT_TRUE(c2.Active()); | ||||
|  | ||||
|   container.SetActiveChild(&c0); | ||||
|   EXPECT_EQ(container.ActiveChild(), &c0); | ||||
|   EXPECT_TRUE(c0.Focused()); | ||||
|   EXPECT_TRUE(c0.Active()); | ||||
|   EXPECT_FALSE(c1.Focused()); | ||||
|   EXPECT_FALSE(c1.Active()); | ||||
|   EXPECT_FALSE(c2.Focused()); | ||||
|   EXPECT_FALSE(c2.Active()); | ||||
| } | ||||
|  | ||||
| TEST(ContainerTest, TakeFocus) { | ||||
|   auto c= Container::Horizontal(); | ||||
|   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(); | ||||
|  | ||||
|   c.Add(&c1); | ||||
|   c.Add(&c2); | ||||
|   c.Add(&c3); | ||||
|   c1.Add(&c11); | ||||
|   c1.Add(&c12); | ||||
|   c1.Add(&c13); | ||||
|   c2.Add(&c21); | ||||
|   c2.Add(&c22); | ||||
|   c2.Add(&c23); | ||||
|  | ||||
|   EXPECT_TRUE(c.Focused()); | ||||
|   EXPECT_TRUE(c1.Focused()); | ||||
|   EXPECT_FALSE(c2.Focused()); | ||||
|   EXPECT_TRUE(c11.Focused()); | ||||
|   EXPECT_FALSE(c12.Focused()); | ||||
|   EXPECT_FALSE(c13.Focused()); | ||||
|   EXPECT_FALSE(c21.Focused()); | ||||
|   EXPECT_FALSE(c22.Focused()); | ||||
|   EXPECT_FALSE(c23.Focused()); | ||||
|   EXPECT_TRUE(c.Active()); | ||||
|   EXPECT_TRUE(c1.Active()); | ||||
|   EXPECT_FALSE(c2.Active()); | ||||
|   EXPECT_TRUE(c11.Active()); | ||||
|   EXPECT_FALSE(c12.Active()); | ||||
|   EXPECT_FALSE(c13.Active()); | ||||
|   EXPECT_TRUE(c21.Active()); | ||||
|   EXPECT_FALSE(c22.Active()); | ||||
|   EXPECT_FALSE(c23.Active()); | ||||
|  | ||||
|   c22.TakeFocus(); | ||||
|   EXPECT_TRUE(c.Focused()); | ||||
|   EXPECT_FALSE(c1.Focused()); | ||||
|   EXPECT_TRUE(c2.Focused()); | ||||
|   EXPECT_FALSE(c11.Focused()); | ||||
|   EXPECT_FALSE(c12.Focused()); | ||||
|   EXPECT_FALSE(c13.Focused()); | ||||
|   EXPECT_FALSE(c21.Focused()); | ||||
|   EXPECT_TRUE(c22.Focused()); | ||||
|   EXPECT_FALSE(c23.Focused()); | ||||
|   EXPECT_TRUE(c.Active()); | ||||
|   EXPECT_FALSE(c1.Active()); | ||||
|   EXPECT_TRUE(c2.Active()); | ||||
|   EXPECT_TRUE(c11.Active()); | ||||
|   EXPECT_FALSE(c12.Active()); | ||||
|   EXPECT_FALSE(c13.Active()); | ||||
|   EXPECT_FALSE(c21.Active()); | ||||
|   EXPECT_TRUE(c22.Active()); | ||||
|   EXPECT_FALSE(c23.Active()); | ||||
|  | ||||
|   c1.TakeFocus(); | ||||
|   EXPECT_TRUE(c.Focused()); | ||||
|   EXPECT_TRUE(c1.Focused()); | ||||
|   EXPECT_FALSE(c2.Focused()); | ||||
|   EXPECT_TRUE(c11.Focused()); | ||||
|   EXPECT_FALSE(c12.Focused()); | ||||
|   EXPECT_FALSE(c13.Focused()); | ||||
|   EXPECT_FALSE(c21.Focused()); | ||||
|   EXPECT_FALSE(c22.Focused()); | ||||
|   EXPECT_FALSE(c23.Focused()); | ||||
|   EXPECT_TRUE(c.Active()); | ||||
|   EXPECT_TRUE(c1.Active()); | ||||
|   EXPECT_FALSE(c2.Active()); | ||||
|   EXPECT_TRUE(c11.Active()); | ||||
|   EXPECT_FALSE(c12.Active()); | ||||
|   EXPECT_FALSE(c13.Active()); | ||||
|   EXPECT_FALSE(c21.Active()); | ||||
|   EXPECT_TRUE(c22.Active()); | ||||
|   EXPECT_FALSE(c23.Active()); | ||||
| } | ||||
|  | ||||
| // Copyright 2020 Arthur Sonzogni. All rights reserved. | ||||
| // Use of this source code is governed by the MIT license that can be found in | ||||
| // the LICENSE file. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user