Add non focusable components. (#172)

This commit is contained in:
Arthur Sonzogni 2021-08-05 22:40:40 +02:00 committed by GitHub
parent 49e8cc57d3
commit 26e26fd41a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 99 additions and 23 deletions

View File

@ -51,6 +51,11 @@ class ComponentBase {
// root component contains this object.
virtual Component ActiveChild();
// Return true when the component contains focusable elements.
// The non focusable Component will be skipped when navigating using the
// keyboard.
virtual bool Focusable() const;
// Whether this is the active child of its parent.
bool Active() const;
// Whether all the ancestors are active.

View File

@ -53,6 +53,10 @@ class ButtonBase : public ComponentBase {
return false;
}
bool Focusable() const final {
return true;
}
private:
ConstStringRef label_;
std::function<void()> on_click_;

View File

@ -71,6 +71,10 @@ class CheckboxBase : public ComponentBase {
return false;
}
bool Focusable() const final {
return true;
}
ConstStringRef label_;
bool* const state_;
Box box_;

View File

@ -106,6 +106,18 @@ Component ComponentBase::ActiveChild() {
return children_.empty() ? nullptr : children_.front();
}
/// @brief Return true when the component contains focusable elements.
/// The non focusable Components will be skipped when navigating using the
/// keyboard.
/// @ingroup component
bool ComponentBase::Focusable() const {
for (const Component& child : children_) {
if (child->Focusable())
return true;
}
return false;
}
/// @brief Returns if the element if the currently active child of its parent.
/// @ingroup component
bool ComponentBase::Active() const {

View File

@ -63,6 +63,25 @@ class ContainerBase : public ComponentBase {
int selected_ = 0;
int* selector_ = nullptr;
void MoveSelector(int dir) {
for (int i = *selector_ + dir; i >= 0 && i < (int)children_.size();
i += dir) {
if (children_[i]->Focusable()) {
*selector_ = i;
return;
}
}
}
void MoveSelectorWrap(int dir) {
for (size_t offset = 1; offset < children_.size(); ++offset) {
int i = (*selector_ + offset * dir + children_.size()) % children_.size();
if (children_[i]->Focusable()) {
*selector_ = i;
return;
}
}
}
};
class VerticalContainer : public ContainerBase {
@ -81,13 +100,13 @@ class VerticalContainer : public ContainerBase {
bool EventHandler(Event event) override {
int old_selected = *selector_;
if (event == Event::ArrowUp || event == Event::Character('k'))
(*selector_)--;
MoveSelector(-1);
if (event == Event::ArrowDown || event == Event::Character('j'))
(*selector_)++;
MoveSelector(+1);
if (event == Event::Tab && children_.size())
*selector_ = (*selector_ + 1) % children_.size();
MoveSelectorWrap(+1);
if (event == Event::TabReverse && children_.size())
*selector_ = (*selector_ + children_.size() - 1) % children_.size();
MoveSelectorWrap(-1);
*selector_ = std::max(0, std::min(int(children_.size()) - 1, *selector_));
return old_selected != *selector_;
@ -110,13 +129,13 @@ class HorizontalContainer : public ContainerBase {
bool EventHandler(Event event) override {
int old_selected = *selector_;
if (event == Event::ArrowLeft || event == Event::Character('h'))
(*selector_)--;
MoveSelector(-1);
if (event == Event::ArrowRight || event == Event::Character('l'))
(*selector_)++;
MoveSelector(+1);
if (event == Event::Tab && children_.size())
*selector_ = (*selector_ + 1) % children_.size();
MoveSelectorWrap(+1);
if (event == Event::TabReverse && children_.size())
*selector_ = (*selector_ + children_.size() - 1) % children_.size();
MoveSelectorWrap(-1);
*selector_ = std::max(0, std::min(int(children_.size()) - 1, *selector_));
return old_selected != *selector_;

View File

@ -10,14 +10,24 @@
using namespace ftxui;
Component Focusable() {
return Button(L"", [] {});
}
Component NonFocusable() {
return Container::Horizontal({});
}
TEST(ContainerTest, HorizontalEvent) {
auto container = Container::Horizontal({});
auto c0 = Container::Horizontal({});
auto c1 = Container::Horizontal({});
auto c2 = Container::Horizontal({});
auto c0 = Focusable();
auto c1 = Focusable();
auto c2 = Focusable();
container->Add(c0);
container->Add(c1);
container->Add(NonFocusable());
container->Add(NonFocusable());
container->Add(c2);
container->Add(NonFocusable());
// With arrow key.
EXPECT_EQ(container->ActiveChild(), c0);
@ -85,12 +95,15 @@ TEST(ContainerTest, HorizontalEvent) {
TEST(ContainerTest, VerticalEvent) {
auto container = Container::Vertical({});
auto c0 = Container::Horizontal({});
auto c1 = Container::Horizontal({});
auto c2 = Container::Horizontal({});
auto c0 = Focusable();
auto c1 = Focusable();
auto c2 = Focusable();
container->Add(c0);
container->Add(c1);
container->Add(NonFocusable());
container->Add(NonFocusable());
container->Add(c2);
container->Add(NonFocusable());
// With arrow key.
EXPECT_EQ(container->ActiveChild(), c0);
@ -158,9 +171,9 @@ TEST(ContainerTest, VerticalEvent) {
TEST(ContainerTest, SetActiveChild) {
auto container = Container::Horizontal({});
auto c0 = Container::Horizontal({});
auto c1 = Container::Horizontal({});
auto c2 = Container::Horizontal({});
auto c0 = Focusable();
auto c1 = Focusable();
auto c2 = Focusable();
container->Add(c0);
container->Add(c1);
container->Add(c2);
@ -215,12 +228,12 @@ TEST(ContainerTest, TakeFocus) {
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({});
auto c11 = Focusable();
auto c12 = Focusable();
auto c13 = Focusable();
auto c21 = Focusable();
auto c22 = Focusable();
auto c23 = Focusable();
c->Add(c1);
c->Add(c2);

View File

@ -163,6 +163,11 @@ class InputBase : public ComponentBase {
}
return true;
}
bool Focusable() const final {
return true;
}
StringRef content_;
ConstStringRef placeholder_;

View File

@ -106,6 +106,10 @@ class MenuBase : public ComponentBase {
return false;
}
bool Focusable() const final {
return entries_->size();
}
int& focused_entry() { return option_->focused_entry(); }
protected:

View File

@ -119,6 +119,10 @@ class RadioboxBase : public ComponentBase {
return false;
}
bool Focusable() const final {
return entries_->size();
}
int& focused_entry() { return option_->focused_entry(); }
const std::vector<std::wstring>* const entries_;

View File

@ -84,6 +84,10 @@ class SliderBase : public ComponentBase {
return false;
}
bool Focusable() const final {
return true;
}
private:
StringRef label_;
T* value_;

View File

@ -107,6 +107,8 @@ class ToggleBase : public ComponentBase {
return false;
}
bool Focusable() const final { return entries_->size(); }
int& focused_entry() { return option_->focused_entry(); }
const std::vector<std::wstring>* const entries_;