From c5ef0c7fb5e2e5f491e639b6e1bceee6b70f6221 Mon Sep 17 00:00:00 2001 From: Arthur Sonzogni Date: Sun, 26 Sep 2021 15:19:17 +0200 Subject: [PATCH] feat: Dropdown select menu. (#214) Dom - `vscroll_indicator`. Show a scrollback indicator on the right. Component - `Maybe`: Display an component conditionnally based on a boolean. - `Dropdown`: A dropdown select list. This address: https://github.com/ArthurSonzogni/FTXUI/issues/204 --- CHANGELOG.md | 9 +++++ CMakeLists.txt | 5 ++- examples/component/CMakeLists.txt | 8 ++-- examples/component/dropdown.cpp | 48 ++++++++++++++++++++++++ examples/component/maybe.cpp | 47 +++++++++++++++++++++++ include/ftxui/component/component.hpp | 2 + include/ftxui/component/event.hpp | 3 +- include/ftxui/dom/elements.hpp | 2 + src/ftxui/component/dropdown.cpp | 54 +++++++++++++++++++++++++++ src/ftxui/component/maybe.cpp | 31 +++++++++++++++ src/ftxui/component/show.cpp | 26 +++++++++++++ src/ftxui/dom/scroll_indicator.cpp | 53 ++++++++++++++++++++++++++ 12 files changed, 282 insertions(+), 6 deletions(-) create mode 100644 examples/component/dropdown.cpp create mode 100644 examples/component/maybe.cpp create mode 100644 src/ftxui/component/dropdown.cpp create mode 100644 src/ftxui/component/maybe.cpp create mode 100644 src/ftxui/component/show.cpp create mode 100644 src/ftxui/dom/scroll_indicator.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index ad79bee..03ffb62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## Current + +### Dom + - `vscroll_indicator`. Show a scrollback indicator on the right. + +### Component + - `Maybe`: Display an component conditionnally based on a boolean. + - `Dropdown`: A dropdown select list. + ## 0.9 (2021-09-26) The initial release where changelog where written. diff --git a/CMakeLists.txt b/CMakeLists.txt index b215c56..1f411d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,14 +57,15 @@ add_library(dom STATIC src/ftxui/dom/frame.cpp src/ftxui/dom/gauge.cpp src/ftxui/dom/graph.cpp - src/ftxui/dom/hbox.cpp src/ftxui/dom/gridbox.cpp + src/ftxui/dom/hbox.cpp src/ftxui/dom/hflow.cpp src/ftxui/dom/inverted.cpp src/ftxui/dom/node.cpp src/ftxui/dom/node_decorator.cpp src/ftxui/dom/paragraph.cpp src/ftxui/dom/reflect.cpp + src/ftxui/dom/scroll_indicator.cpp src/ftxui/dom/separator.cpp src/ftxui/dom/size.cpp src/ftxui/dom/spinner.cpp @@ -87,8 +88,10 @@ add_library(component STATIC src/ftxui/component/checkbox.cpp src/ftxui/component/component.cpp src/ftxui/component/container.cpp + src/ftxui/component/dropdown.cpp src/ftxui/component/event.cpp src/ftxui/component/input.cpp + src/ftxui/component/maybe.cpp src/ftxui/component/menu.cpp src/ftxui/component/radiobox.cpp src/ftxui/component/radiobox.cpp diff --git a/examples/component/CMakeLists.txt b/examples/component/CMakeLists.txt index 7e02bf0..299e45b 100644 --- a/examples/component/CMakeLists.txt +++ b/examples/component/CMakeLists.txt @@ -2,19 +2,21 @@ set(DIRECTORY_LIB component) example(button) example(checkbox) -example(nested_screen) example(checkbox_in_frame) example(composition) +example(dropdown) example(gallery) example(homescreen) example(input) +example(maybe) example(menu) -example(menu_in_frame) example(menu2) -example(menu_multiple) example(menu_entries) +example(menu_in_frame) +example(menu_multiple) example(menu_style) example(modal_dialog) +example(nested_screen) example(print_key_press) example(radiobox) example(radiobox_in_frame) diff --git a/examples/component/dropdown.cpp b/examples/component/dropdown.cpp new file mode 100644 index 0000000..393ce1c --- /dev/null +++ b/examples/component/dropdown.cpp @@ -0,0 +1,48 @@ +#include // for function +#include // for basic_ostream::operator<<, operator<<, endl, basic_ostream, basic_ostream<>::__ostream_type, cout, ostream +#include // for string, basic_string, allocator +#include // for vector + +#include "ftxui/component/captured_mouse.hpp" // for ftxui +#include "ftxui/component/component.hpp" // for Menu +#include "ftxui/component/component_options.hpp" // for MenuOption +#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive + +int main(int argc, const char* argv[]) { + using namespace ftxui; + + std::vector entries = { + "tribute", "clearance", "ally", "bend", "electronics", + "module", "era", "cultural", "sniff", "nationalism", + "negotiation", "deliver", "figure", "east", + "tribute", "clearance", "ally", "bend", "electronics", + "module", "era", "cultural", "sniff", "nationalism", + "negotiation", "deliver", "figure", "east", + "tribute", "clearance", "ally", "bend", "electronics", + "module", "era", "cultural", "sniff", "nationalism", + "negotiation", "deliver", "figure", "east", + }; + + int selected_1 = 0; + int selected_2 = 0; + int selected_3 = 0; + int selected_4 = 0; + + auto layout = Container::Vertical({ + Container::Horizontal({ + Dropdown(&entries, &selected_1), + Dropdown(&entries, &selected_2), + }), + Container::Horizontal({ + Dropdown(&entries, &selected_3), + Dropdown(&entries, &selected_4), + }), + }); + + auto screen = ScreenInteractive::FitComponent(); + screen.Loop(layout); +} + +// 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/examples/component/maybe.cpp b/examples/component/maybe.cpp new file mode 100644 index 0000000..291505b --- /dev/null +++ b/examples/component/maybe.cpp @@ -0,0 +1,47 @@ +#include // for function +#include // for basic_ostream::operator<<, operator<<, endl, basic_ostream, basic_ostream<>::__ostream_type, cout, ostream +#include // for string, basic_string, allocator +#include // for vector + +#include "ftxui/component/captured_mouse.hpp" // for ftxui +#include "ftxui/component/component.hpp" // for Menu +#include "ftxui/component/component_options.hpp" // for MenuOption +#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive + +using namespace ftxui; +Component Border(Component child) { + return Renderer(child, [child] { return child->Render() | border; }); +} + +int main(int argc, const char* argv[]) { + + std::vector entries = { + "entry 1", + "entry 2", + "entry 3", + }; + int menu_1_selected = 0; + int menu_2_selected = 0; + auto menu_1 = Radiobox(&entries, &menu_1_selected); + auto menu_2 = Radiobox(&entries, &menu_2_selected); + + menu_1 = Border(menu_1); + menu_2 = Border(menu_2); + + bool menu_1_show = false; + bool menu_2_show = false; + + auto layout = Container::Vertical({ + Checkbox("Show menu_1", &menu_1_show), + Maybe(menu_1, &menu_1_show), + Checkbox("Show menu_2", &menu_2_show), + Maybe(menu_2, &menu_2_show), + }); + + auto screen = ScreenInteractive::TerminalOutput(); + screen.Loop(layout); +} + +// 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/include/ftxui/component/component.hpp b/include/ftxui/component/component.hpp index ea037aa..711ac8f 100644 --- a/include/ftxui/component/component.hpp +++ b/include/ftxui/component/component.hpp @@ -39,6 +39,7 @@ Component Menu(ConstStringListRef entries, int* selected_, Ref = {}); Component MenuEntry(ConstStringRef label, Ref = {}); +Component Dropdown(ConstStringListRef entries, int* selected); Component Radiobox(ConstStringListRef entries, int* selected_, Ref option = {}); @@ -55,6 +56,7 @@ Component Renderer(Component child, std::function); Component Renderer(std::function); Component Renderer(std::function); Component CatchEvent(Component child, std::function); +Component Maybe(Component, bool* show); namespace Container { Component Vertical(Components children); diff --git a/include/ftxui/component/event.hpp b/include/ftxui/component/event.hpp index ef6190a..7d8b2a3 100644 --- a/include/ftxui/component/event.hpp +++ b/include/ftxui/component/event.hpp @@ -74,6 +74,7 @@ struct Event { bool operator!=(const Event& other) const { return !operator==(other); } //--- State section ---------------------------------------------------------- + ScreenInteractive* screen_ = nullptr; private: friend ComponentBase; friend ScreenInteractive; @@ -95,8 +96,6 @@ struct Event { struct Cursor cursor_; }; std::string input_; - - ScreenInteractive* screen_ = nullptr; }; } // namespace ftxui diff --git a/include/ftxui/dom/elements.hpp b/include/ftxui/dom/elements.hpp index c3dbc3f..6d86df8 100644 --- a/include/ftxui/dom/elements.hpp +++ b/include/ftxui/dom/elements.hpp @@ -104,6 +104,8 @@ Element yframe(Element); Element focus(Element); Element select(Element); +Element vscroll_indicator(Element); + // --- Util -------------------------------------------------------------------- Element hcenter(Element); Element vcenter(Element); diff --git a/src/ftxui/component/dropdown.cpp b/src/ftxui/component/dropdown.cpp new file mode 100644 index 0000000..839cb64 --- /dev/null +++ b/src/ftxui/component/dropdown.cpp @@ -0,0 +1,54 @@ +#include "ftxui/component/component.hpp" +#include "ftxui/component/component_base.hpp" +#include "ftxui/component/event.hpp" + +namespace ftxui { + +Component Dropdown(ConstStringListRef entries, int* selected) { + class Impl : public ComponentBase { + public: + Impl(ConstStringListRef entries, int* selected) + : entries_(std::move(entries)), selected_(selected) { + CheckboxOption option; + option.style_checked = "↓│"; + option.style_unchecked = "→│"; + checkbox_ = Checkbox(&title_, &show_, option), + radiobox_ = Radiobox(entries_, selected_); + + Add(Container::Vertical({ + checkbox_, + Maybe(radiobox_, &show_), + })); + } + + Element Render() override { + title_ = entries_[*selected_]; + if (show_) { + return vbox({ + checkbox_->Render(), + separator(), + radiobox_->Render() | vscroll_indicator | frame | + size(HEIGHT, LESS_THAN, 12), + }) | + border; + } + + return vbox({ + checkbox_->Render() | border, + filler(), + }); + } + + private: + ConstStringListRef entries_; + bool show_ = false; + int* selected_; + std::string title_; + Component checkbox_; + Component radiobox_; + }; + + return Make(std::move(entries), selected); +} + +} // namespace ftxui diff --git a/src/ftxui/component/maybe.cpp b/src/ftxui/component/maybe.cpp new file mode 100644 index 0000000..c1048a0 --- /dev/null +++ b/src/ftxui/component/maybe.cpp @@ -0,0 +1,31 @@ +#include "ftxui/component/component.hpp" +#include "ftxui/component/component_base.hpp" +#include "ftxui/component/event.hpp" + +namespace ftxui { + +Component Maybe(Component child, bool* show) { + class Impl : public ComponentBase { + public: + Impl(bool* show): show_(show) {} + + private: + Element Render() override { + return *show_ ? ComponentBase::Render() : std::make_unique(); + } + bool Focusable() const override { + return *show_ && ComponentBase::Focusable(); + } + bool OnEvent(Event event) override { + return *show_ && ComponentBase::OnEvent(event); + } + + bool* show_; + }; + + auto maybe = Make(show); + maybe->Add(std::move(child)); + return maybe; +} + +} // namespace ftxui diff --git a/src/ftxui/component/show.cpp b/src/ftxui/component/show.cpp new file mode 100644 index 0000000..15b8388 --- /dev/null +++ b/src/ftxui/component/show.cpp @@ -0,0 +1,26 @@ +#include "ftxui/component/component_base.hpp" + +Component Maybe(Component child, bool* show) { + class Impl : public ComponentBase { + public: + Impl(Component child, bool* show) : ComponentBase(child), show_(show) {} + + private: + Element Render() override { + if (*show_) + return ComponentBase::Render(); + else + return text(""); + } + bool Focusable() const override { + return *show_ && ComponentBase::Focusable(); + } + bool OnEvent(Event event) override { + if (*show_) + return false return ComponentBase::OnEvent(event); + } + + bool* show_; + }; + return Make(std::move(child), show); +} diff --git a/src/ftxui/dom/scroll_indicator.cpp b/src/ftxui/dom/scroll_indicator.cpp new file mode 100644 index 0000000..fe8a67a --- /dev/null +++ b/src/ftxui/dom/scroll_indicator.cpp @@ -0,0 +1,53 @@ +#include "ftxui/dom/elements.hpp" +#include "ftxui/dom/node.hpp" +#include "ftxui/dom/node_decorator.hpp" // for NodeDecorator +#include "ftxui/screen/box.hpp" +#include "ftxui/screen/screen.hpp" + +namespace ftxui { + +/// @brief Add a filter that will invert the foreground and the background +/// colors. +/// @ingroup dom +Element vscroll_indicator(Element child) { + class Impl : public NodeDecorator { + using NodeDecorator::NodeDecorator; + + void Render(Screen& screen) final { + Node::Render(screen); + + const Box& stencil = screen.stencil; + + float size_inner = box_.y_max - box_.y_min; + float size_outter = stencil.y_max - stencil.y_min; + float start_y = stencil.y_min + + (stencil.y_min - box_.y_min) * size_outter / size_inner; + float end_y = stencil.y_min + + (stencil.y_max - box_.y_min) * size_outter / size_inner; + + const int x = stencil.x_max; + for (int y = stencil.y_min; y <= stencil.y_max; ++y) { + bool up = (2 * y + -1 >= 2 * start_y) && (2 * y + -1 <= 2 * end_y); + bool down = (2 * y + 0 >= 2 * start_y) && (2 * y + 0 <= 2 * end_y); + + if (up) { + if (down) { + screen.PixelAt(x, y).character = "┃"; + } else { + screen.PixelAt(x, y).character = "╹"; + } + } else { + if (down) { + screen.PixelAt(x, y).character = "╻"; + } else { + screen.PixelAt(x, y).character = " "; + } + } + screen.PixelAt(x,y).inverted = true; + } + }; + }; + return std::make_shared(std::move(child)); +} + +} // namespace ftxui