From 81d79d311de8824a40b6a40614ff266abe7a0d34 Mon Sep 17 00:00:00 2001 From: ArthurSonzogni Date: Wed, 26 Aug 2020 14:57:42 +0200 Subject: [PATCH] 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. --- CMakeLists.txt | 2 +- include/ftxui/component/component.hpp | 7 + include/ftxui/component/container.hpp | 1 + src/ftxui/component/component.cpp | 24 +++ src/ftxui/component/container.cpp | 9 ++ src/ftxui/component/container_test.cpp | 205 +++++++++++++++++++++++++ 6 files changed, 247 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2708ac0..f3ac6a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/include/ftxui/component/component.hpp b/include/ftxui/component/component.hpp index 5f26e75..2b7af38 100644 --- a/include/ftxui/component/component.hpp +++ b/include/ftxui/component/component.hpp @@ -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(); diff --git a/include/ftxui/component/container.hpp b/include/ftxui/component/container.hpp index 2f14b13..7054be1 100644 --- a/include/ftxui/component/container.hpp +++ b/include/ftxui/component/container.hpp @@ -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 diff --git a/src/ftxui/component/component.cpp b/src/ftxui/component/component.cpp index 63e7ede..a40c93f 100644 --- a/src/ftxui/component/component.cpp +++ b/src/ftxui/component/component.cpp @@ -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 diff --git a/src/ftxui/component/container.cpp b/src/ftxui/component/container.cpp index 58abcc7..31f1f39 100644 --- a/src/ftxui/component/container.cpp +++ b/src/ftxui/component/container.cpp @@ -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')) diff --git a/src/ftxui/component/container_test.cpp b/src/ftxui/component/container_test.cpp index e28646e..ea5752e 100644 --- a/src/ftxui/component/container_test.cpp +++ b/src/ftxui/component/container_test.cpp @@ -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.