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 2020-08-26 14:57:42 +02:00 committed by Arthur Sonzogni
parent 114ab4ae2a
commit 81d79d311d
6 changed files with 247 additions and 1 deletions

View File

@ -9,7 +9,7 @@ execute_process(
project(ftxui project(ftxui
LANGUAGES CXX LANGUAGES CXX
VERSION 0.1.${git_version} VERSION 0.2.${git_version}
) )
option(FTXUI_BUILD_EXAMPLES "Set to ON to build examples" ON) option(FTXUI_BUILD_EXAMPLES "Set to ON to build examples" ON)

View File

@ -39,11 +39,18 @@ class Component {
// We say an element has the focus if the chain of ActiveChild() from the // We say an element has the focus if the chain of ActiveChild() from the
// root component contains this object. // root component contains this object.
virtual Component* ActiveChild(); virtual Component* ActiveChild();
// Whether this is the active child of its parent. // Whether this is the active child of its parent.
bool Active(); bool Active();
// Whether all the ancestors are active. // Whether all the ancestors are active.
bool Focused(); 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: private:
Component* parent_ = nullptr; Component* parent_ = nullptr;
void Detach(); void Detach();

View File

@ -18,6 +18,7 @@ class Container : public Component {
bool OnEvent(Event event) override; bool OnEvent(Event event) override;
Element Render() override; Element Render() override;
Component* ActiveChild() override; Component* ActiveChild() override;
virtual void SetActiveChild(Component*) override;
protected: protected:
// Handlers // Handlers

View File

@ -58,9 +58,16 @@ Component* Component::ActiveChild() {
return children_.empty() ? nullptr : children_.front(); 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. /// @brief Returns if the elements if focused by the user.
/// True when the Component is focused by the user. An element is Focused when /// 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. /// it is with all its ancestors the ActiveChild() of their parents.
/// @ingroup component
bool Component::Focused() { bool Component::Focused() {
Component* current = this; Component* current = this;
for (;;) { 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. /// @brief Detach this children from its parent.
/// @see Attach /// @see Attach
/// @see Detach /// @see Detach

View File

@ -47,6 +47,15 @@ Component* Container::ActiveChild() {
return children_[selected % children_.size()]; 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) { bool Container::VerticalEvent(Event event) {
int old_selected = selected_; int old_selected = selected_;
if (event == Event::ArrowUp || event == Event::Character('k')) if (event == Event::ArrowUp || event == Event::Character('k'))

View File

@ -75,6 +75,211 @@ TEST(ContainerTest, HorizontalEvent) {
container.OnEvent(Event::TabReverse); 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. // Copyright 2020 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in // Use of this source code is governed by the MIT license that can be found in
// the LICENSE file. // the LICENSE file.