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
|
||||
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.
|
||||
|
Loading…
Reference in New Issue
Block a user