mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2024-11-22 18:59:59 +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:
parent
114ab4ae2a
commit
81d79d311d
@ -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)
|
||||||
|
@ -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();
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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'))
|
||||||
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user