From 20f16b39844bfe3d9b6c27f4c8f2c80ae4efde78 Mon Sep 17 00:00:00 2001 From: Arthur Sonzogni Date: Sat, 19 Feb 2022 11:49:12 +0100 Subject: [PATCH] Improve ComponentBase and Container::Tab Focusable implementations (#341) - Provide better defaults for ComponentBase `Focusable()` and `ActiveChild()` methods. This resolves: https://github.com/ArthurSonzogni/FTXUI/issues/335 - Implement `Container::Tab` 's `Focusable()` methods. This prevents the users to navigate into a tab with no interactivity. --- CHANGELOG.md | 3 +++ src/ftxui/component/component.cpp | 11 +++++++--- src/ftxui/component/component_test.cpp | 17 ++++++++++++++++ src/ftxui/component/container.cpp | 6 ++++++ src/ftxui/component/container_test.cpp | 28 ++++++++++++++++++++++++++ 5 files changed, 62 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 232f2464..21253650 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,9 @@ Element gaugeDirection(float ratio, GaugeDirection); ### Component - Support SIGTSTP. (ctrl+z). - Support task posting. `ScreenInteractive::Post(Task)`. +- **bugfix** Container::Tab implements `Focusable()`. +- **bugfix** Improved default implementations of ComponentBase `Focusable()` and + `ActiveChild()` methods. 2.0.0 ----- diff --git a/src/ftxui/component/component.cpp b/src/ftxui/component/component.cpp index 3bd9e90a..76357db4 100644 --- a/src/ftxui/component/component.cpp +++ b/src/ftxui/component/component.cpp @@ -105,7 +105,11 @@ bool ComponentBase::OnEvent(Event event) { /// @return the currently Active child. /// @ingroup component Component ComponentBase::ActiveChild() { - return children_.empty() ? nullptr : children_.front(); + for (auto& child : children_) { + if (child->Focusable()) + return child; + } + return nullptr; } /// @brief Return true when the component contains focusable elements. @@ -128,14 +132,15 @@ bool ComponentBase::Active() const { /// @brief Returns if the elements if focused by the user. /// True when the ComponentBase is focused by the user. An element is Focused -/// when it is with all its ancestors the ActiveChild() of their parents. +/// when it is with all its ancestors the ActiveChild() of their parents, and it +/// Focusable(). /// @ingroup component bool ComponentBase::Focused() const { auto current = this; while (current && current->Active()) { current = current->parent_; } - return !current; + return !current && Focusable(); } /// @brief Make the |child| to be the "active" one. diff --git a/src/ftxui/component/component_test.cpp b/src/ftxui/component/component_test.cpp index 468a5197..42a68145 100644 --- a/src/ftxui/component/component_test.cpp +++ b/src/ftxui/component/component_test.cpp @@ -3,6 +3,7 @@ #include // for shared_ptr, __shared_ptr_access, allocator, make_shared #include "ftxui/component/captured_mouse.hpp" // for ftxui +#include "ftxui/component/component.hpp" // for Make #include "ftxui/component/component_base.hpp" // for ComponentBase, Component #include "gtest/gtest_pred_impl.h" // for EXPECT_EQ, Test, SuiteApiResolver, TEST, TestFactoryImpl @@ -155,6 +156,22 @@ TEST(ContainerTest, ChildAt) { EXPECT_EQ(parent->ChildAt(0u), child_2); } +TEST(ComponentTest, NonFocusableAreNotFocused) { + class NonFocusable : public ComponentBase { + bool Focusable() const override { return false; } + }; + auto root = Make(); + EXPECT_FALSE(root->Focused()); + EXPECT_EQ(root->ActiveChild(), nullptr); + + auto child = Make(); + root->Add(child); + EXPECT_FALSE(root->Focused()); + EXPECT_FALSE(child->Focused()); + EXPECT_EQ(root->ActiveChild(), nullptr); + EXPECT_EQ(child->ActiveChild(), nullptr); +} + // 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. diff --git a/src/ftxui/component/container.cpp b/src/ftxui/component/container.cpp index 400884a4..246d4565 100644 --- a/src/ftxui/component/container.cpp +++ b/src/ftxui/component/container.cpp @@ -190,6 +190,12 @@ class TabContainer : public ContainerBase { return text("Empty container"); } + bool Focusable() const override { + if (children_.size() == 0) + return false; + return children_[*selector_ % children_.size()]->Focusable(); + } + bool OnMouseEvent(Event event) override { return ActiveChild()->OnEvent(event); } diff --git a/src/ftxui/component/container_test.cpp b/src/ftxui/component/container_test.cpp index 700dbe34..1c5d9143 100644 --- a/src/ftxui/component/container_test.cpp +++ b/src/ftxui/component/container_test.cpp @@ -305,6 +305,34 @@ TEST(ContainerTest, TakeFocus) { EXPECT_FALSE(c23->Active()); } +TEST(ContainerTest, TabFocusable) { + int selected = 0; + auto c = Container::Tab( + { + Focusable(), + NonFocusable(), + Focusable(), + NonFocusable(), + }, + &selected); + + selected = 0; + EXPECT_TRUE(c->Focusable()); + EXPECT_TRUE(c->Focused()); + + selected = 1; + EXPECT_FALSE(c->Focusable()); + EXPECT_FALSE(c->Focused()); + + selected = 2; + EXPECT_TRUE(c->Focusable()); + EXPECT_TRUE(c->Focused()); + + selected = 3; + EXPECT_FALSE(c->Focusable()); + EXPECT_FALSE(c->Focused()); +} + // 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.