From 9b074d1e27a9fa069d55dee23d75fd8ce4df885a Mon Sep 17 00:00:00 2001 From: Arthur Sonzogni Date: Thu, 9 Mar 2023 20:21:23 +0100 Subject: [PATCH] Feature resizable spilt with custom separator (#583) * Feature: ResizableSplit with custom separator This resolves: https://github.com/ArthurSonzogni/FTXUI/issues/580 Co-authored-by: Pin Loon Lee --- CHANGELOG.md | 8 + CMakeLists.txt | 1 + examples/component/slider_direction.cpp | 11 +- include/ftxui/component/component.hpp | 1 + include/ftxui/component/component_options.hpp | 27 +- include/ftxui/dom/direction.hpp | 17 + include/ftxui/dom/elements.hpp | 8 +- src/ftxui/component/menu.cpp | 69 ++-- src/ftxui/component/menu_test.cpp | 13 +- src/ftxui/component/resizable_split.cpp | 318 +++++++----------- src/ftxui/component/resizable_split_test.cpp | 107 +++++- src/ftxui/component/slider.cpp | 57 ++-- src/ftxui/component/slider_test.cpp | 11 +- src/ftxui/dom/gauge.cpp | 39 +-- src/ftxui/dom/size.cpp | 8 +- 15 files changed, 385 insertions(+), 310 deletions(-) create mode 100644 include/ftxui/dom/direction.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index b8584fb..cdab8fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ Changelog current (development) --------------------- +### Component +- Feature: Support `ResizableSplit` with customizable separator. +- Breaking: MenuDirection enum is renamed Direction + +### +- Breaking: Direction enum is renamed WidthOrHeight +- Breaking: GaugeDirection enum is renamed Direction + 4.0.0 ----- diff --git a/CMakeLists.txt b/CMakeLists.txt index e58a9be..6f28c45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,7 @@ add_library(screen add_library(dom include/ftxui/dom/canvas.hpp + include/ftxui/dom/direction.hpp include/ftxui/dom/elements.hpp include/ftxui/dom/flexbox_config.hpp include/ftxui/dom/node.hpp diff --git a/examples/component/slider_direction.cpp b/examples/component/slider_direction.cpp index 69a0044..f1a4ff5 100644 --- a/examples/component/slider_direction.cpp +++ b/examples/component/slider_direction.cpp @@ -2,9 +2,10 @@ #include // for sin #include // for ComponentBase #include // for SliderOption -#include // for size, GREATER_THAN, GaugeDirection, GaugeDirection::Up, HEIGHT -#include // for ConstRef, Ref -#include // for shared_ptr, __shared_ptr_access +#include // for Direction, Direction::Up +#include // for size, GREATER_THAN, HEIGHT +#include // for ConstRef, Ref +#include // for shared_ptr, __shared_ptr_access #include "ftxui/component/captured_mouse.hpp" // for ftxui #include "ftxui/component/component.hpp" // for Horizontal, Slider, operator|= @@ -26,7 +27,7 @@ int main(int argc, const char* argv[]) { option.value = &values[i]; option.max = 100; option.increment = 5; - option.direction = GaugeDirection::Up; + option.direction = Direction::Up; layout_horizontal->Add(Slider(option)); /* In C++20: @@ -34,7 +35,7 @@ int main(int argc, const char* argv[]) { .value = &values[i], .max = 100, .increment = 5, - .direction = GaugeDirection::Up, + .direction = Direction::Up, })); */ } diff --git a/include/ftxui/component/component.hpp b/include/ftxui/component/component.hpp index 0a8df02..b54f89f 100644 --- a/include/ftxui/component/component.hpp +++ b/include/ftxui/component/component.hpp @@ -92,6 +92,7 @@ Component ResizableSplitLeft(Component main, Component back, int* main_size); Component ResizableSplitRight(Component main, Component back, int* main_size); Component ResizableSplitTop(Component main, Component back, int* main_size); Component ResizableSplitBottom(Component main, Component back, int* main_size); +Component ResizableSplit(ResizableSplitOption options); Component Renderer(Component child, std::function); Component Renderer(std::function); diff --git a/include/ftxui/component/component_options.hpp b/include/ftxui/component/component_options.hpp index 8863ff6..8c49bd6 100644 --- a/include/ftxui/component/component_options.hpp +++ b/include/ftxui/component/component_options.hpp @@ -3,12 +3,14 @@ #include // for milliseconds #include // for Duration, QuadraticInOut, Function -#include // for Element, GaugeDirection, GaugeDirection::Right -#include // for Ref, ConstRef -#include // for function -#include // for optional -#include // for string +#include // for Direction, Direction::Left, Direction::Right, Direction::Down +#include // for Element, separator +#include // for Ref, ConstRef +#include // for function +#include // for optional +#include // for string +#include "ftxui/component/component_base.hpp" // for Component #include "ftxui/screen/color.hpp" // for Color, Color::GrayDark, Color::White namespace ftxui { @@ -87,8 +89,7 @@ struct MenuOption { // Style: UnderlineOption underline; MenuEntryOption entries; - enum Direction { Up, Down, Left, Right }; - Direction direction = Down; + Direction direction = Direction::Down; std::function elements_prefix; std::function elements_infix; std::function elements_postfix; @@ -164,6 +165,16 @@ struct RadioboxOption { Ref focused_entry = 0; }; +struct ResizableSplitOption { + Component main; + Component back; + Ref direction = Direction::Left; + Ref main_size = + (direction() == Direction::Left || direction() == Direction::Right) ? 20 + : 10; + std::function separator_func = [] { return ::ftxui::separator(); }; +}; + // @brief Option for the `Slider` component. // @ingroup component template @@ -172,7 +183,7 @@ struct SliderOption { ConstRef min = T(0); ConstRef max = T(100); ConstRef increment = (max() - min()) / 20; - GaugeDirection direction = GaugeDirection::Right; + Direction direction = Direction::Right; Color color_active = Color::White; Color color_inactive = Color::GrayDark; }; diff --git a/include/ftxui/dom/direction.hpp b/include/ftxui/dom/direction.hpp new file mode 100644 index 0000000..c738718 --- /dev/null +++ b/include/ftxui/dom/direction.hpp @@ -0,0 +1,17 @@ +#ifndef FTXUI_DOM_DIRECTION_HPP +#define FTXUI_DOM_DIRECTION_HPP + +namespace ftxui { +enum class Direction { + Up = 0, + Down = 1, + Left = 2, + Right = 3, +}; + +} // namespace ftxui + +#endif // FTXUI_DOM_DIRECTION_HPP +// Copyright 2023 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/dom/elements.hpp b/include/ftxui/dom/elements.hpp index b3935c5..e8ac059 100644 --- a/include/ftxui/dom/elements.hpp +++ b/include/ftxui/dom/elements.hpp @@ -5,6 +5,7 @@ #include #include "ftxui/dom/canvas.hpp" +#include "ftxui/dom/direction.hpp" #include "ftxui/dom/flexbox_config.hpp" #include "ftxui/dom/node.hpp" #include "ftxui/screen/box.hpp" @@ -21,7 +22,6 @@ using Decorator = std::function; using GraphFunction = std::function(int, int)>; enum BorderStyle { LIGHT, HEAVY, DOUBLE, ROUNDED, EMPTY }; -enum class GaugeDirection { Left, Up, Right, Down }; // Pipe elements into decorator togethers. // For instance the next lines are equivalents: @@ -56,7 +56,7 @@ Element gaugeLeft(float progress); Element gaugeRight(float progress); Element gaugeUp(float progress); Element gaugeDown(float progress); -Element gaugeDirection(float progress, GaugeDirection); +Element gaugeDirection(float progress, Direction direction); Element border(Element); Element borderLight(Element); Element borderHeavy(Element); @@ -124,9 +124,9 @@ Element notflex(Element); // Reset the flex attribute. Element filler(); // A blank expandable element. // -- Size override; -enum Direction { WIDTH, HEIGHT }; +enum WidthOrHeight { WIDTH, HEIGHT }; enum Constraint { LESS_THAN, EQUAL, GREATER_THAN }; -Decorator size(Direction, Constraint, int value); +Decorator size(WidthOrHeight, Constraint, int value); // --- Frame --- // A frame is a scrollable area. The internal area is potentially larger than diff --git a/src/ftxui/component/menu.cpp b/src/ftxui/component/menu.cpp index b1a2332..2bca92e 100644 --- a/src/ftxui/component/menu.cpp +++ b/src/ftxui/component/menu.cpp @@ -1,16 +1,17 @@ -#include // for max, fill_n, reverse -#include // for milliseconds -#include // for function -#include // for allocator_traits<>::value_type, swap -#include // for operator+, string -#include // for move -#include // for vector, __alloc_traits<>::value_type +#include // for max, fill_n, reverse +#include // for milliseconds +#include // for Direction, Direction::Down, Direction::Left, Direction::Right, Direction::Up +#include // for function +#include // for allocator_traits<>::value_type, swap +#include // for operator+, string +#include // for move +#include // for vector, __alloc_traits<>::value_type #include "ftxui/component/animation.hpp" // for Animator, Linear #include "ftxui/component/captured_mouse.hpp" // for CapturedMouse #include "ftxui/component/component.hpp" // for Make, Menu, MenuEntry, Toggle #include "ftxui/component/component_base.hpp" // for ComponentBase -#include "ftxui/component/component_options.hpp" // for MenuOption, MenuEntryOption, MenuOption::Direction, UnderlineOption, AnimatedColorOption, AnimatedColorsOption, EntryState, MenuOption::Down, MenuOption::Left, MenuOption::Right, MenuOption::Up +#include "ftxui/component/component_options.hpp" // for MenuOption, MenuEntryOption, UnderlineOption, AnimatedColorOption, AnimatedColorsOption, EntryState #include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp, Event::Return, Event::Tab, Event::TabReverse #include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Released, Mouse::WheelDown, Mouse::WheelUp, Mouse::None #include "ftxui/component/screen_interactive.hpp" // for Component @@ -36,25 +37,25 @@ Element DefaultOptionTransform(const EntryState& state) { return e; } -bool IsInverted(MenuOption::Direction direction) { +bool IsInverted(Direction direction) { switch (direction) { - case MenuOption::Direction::Up: - case MenuOption::Direction::Left: + case Direction::Up: + case Direction::Left: return true; - case MenuOption::Direction::Down: - case MenuOption::Direction::Right: + case Direction::Down: + case Direction::Right: return false; } return false; // NOT_REACHED() } -bool IsHorizontal(MenuOption::Direction direction) { +bool IsHorizontal(Direction direction) { switch (direction) { - case MenuOption::Direction::Left: - case MenuOption::Direction::Right: + case Direction::Left: + case Direction::Right: return true; - case MenuOption::Direction::Down: - case MenuOption::Direction::Up: + case Direction::Down: + case Direction::Up: return false; } return false; // NOT_REACHED() @@ -178,56 +179,56 @@ class MenuBase : public ComponentBase { void OnUp() { switch (option_->direction) { - case MenuOption::Direction::Up: + case Direction::Up: (*selected_)++; break; - case MenuOption::Direction::Down: + case Direction::Down: (*selected_)--; break; - case MenuOption::Direction::Left: - case MenuOption::Direction::Right: + case Direction::Left: + case Direction::Right: break; } } void OnDown() { switch (option_->direction) { - case MenuOption::Direction::Up: + case Direction::Up: (*selected_)--; break; - case MenuOption::Direction::Down: + case Direction::Down: (*selected_)++; break; - case MenuOption::Direction::Left: - case MenuOption::Direction::Right: + case Direction::Left: + case Direction::Right: break; } } void OnLeft() { switch (option_->direction) { - case MenuOption::Direction::Left: + case Direction::Left: (*selected_)++; break; - case MenuOption::Direction::Right: + case Direction::Right: (*selected_)--; break; - case MenuOption::Direction::Down: - case MenuOption::Direction::Up: + case Direction::Down: + case Direction::Up: break; } } void OnRight() { switch (option_->direction) { - case MenuOption::Direction::Left: + case Direction::Left: (*selected_)--; break; - case MenuOption::Direction::Right: + case Direction::Right: (*selected_)++; break; - case MenuOption::Direction::Down: - case MenuOption::Direction::Up: + case Direction::Down: + case Direction::Up: break; } } diff --git a/src/ftxui/component/menu_test.cpp b/src/ftxui/component/menu_test.cpp index 6634edf..c3689dd 100644 --- a/src/ftxui/component/menu_test.cpp +++ b/src/ftxui/component/menu_test.cpp @@ -1,5 +1,6 @@ #include // for Test, EXPECT_EQ, Message, TestPartResult, TestInfo (ptr only), TEST -#include // for operator""s, chrono_literals +#include // for operator""s, chrono_literals +#include // for Direction, Direction::Down, Direction::Left, Direction::Right, Direction::Up #include // for __shared_ptr_access, shared_ptr, allocator #include // for string, basic_string #include // for vector @@ -7,7 +8,7 @@ #include "ftxui/component/animation.hpp" // for Duration, Params #include "ftxui/component/component.hpp" // for Menu #include "ftxui/component/component_base.hpp" // for ComponentBase -#include "ftxui/component/component_options.hpp" // for MenuOption, MenuOption::Down, MenuOption::Left, MenuOption::Right, MenuOption::Up +#include "ftxui/component/component_options.hpp" // for MenuOption #include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp, Event::Return #include "ftxui/dom/node.hpp" // for Render #include "ftxui/screen/screen.hpp" // for Screen @@ -53,7 +54,7 @@ TEST(MenuTest, DirectionDown) { auto menu = Menu(&entries, &selected, &option); selected = 0; - option.direction = MenuOption::Down; + option.direction = Direction::Down; Screen screen(4, 3); Render(screen, menu->Render()); EXPECT_EQ(screen.ToString(), @@ -80,7 +81,7 @@ TEST(MenuTest, DirectionsUp) { std::vector entries = {"1", "2", "3"}; MenuOption option; auto menu = Menu(&entries, &selected, &option); - option.direction = MenuOption::Up; + option.direction = Direction::Up; Screen screen(4, 3); Render(screen, menu->Render()); EXPECT_EQ(screen.ToString(), @@ -106,7 +107,7 @@ TEST(MenuTest, DirectionsRight) { std::vector entries = {"1", "2", "3"}; MenuOption option; auto menu = Menu(&entries, &selected, &option); - option.direction = MenuOption::Right; + option.direction = Direction::Right; Screen screen(10, 1); Render(screen, menu->Render()); EXPECT_EQ(screen.ToString(), @@ -132,7 +133,7 @@ TEST(MenuTest, DirectionsLeft) { std::vector entries = {"1", "2", "3"}; MenuOption option; auto menu = Menu(&entries, &selected, &option); - option.direction = MenuOption::Left; + option.direction = Direction::Left; Screen screen(10, 1); Render(screen, menu->Render()); EXPECT_EQ(screen.ToString(), diff --git a/src/ftxui/component/resizable_split.cpp b/src/ftxui/component/resizable_split.cpp index 85be4bd..c4973e0 100644 --- a/src/ftxui/component/resizable_split.cpp +++ b/src/ftxui/component/resizable_split.cpp @@ -1,26 +1,28 @@ -#include // for __shared_ptr_access +#include // for ResizableSplitOption +#include // for Direction, Direction::Down, Direction::Left, Direction::Right, Direction::Up +#include // for Ref +#include // for function +#include // for __shared_ptr_access, shared_ptr, allocator #include // for move #include "ftxui/component/captured_mouse.hpp" // for CapturedMouse -#include "ftxui/component/component.hpp" // for Component, Make, Horizontal, Vertical, ResizableSplitBottom, ResizableSplitLeft, ResizableSplitRight, ResizableSplitTop -#include "ftxui/component/component_base.hpp" // for ComponentBase +#include "ftxui/component/component.hpp" // for Horizontal, Make, ResizableSplit, ResizableSplitBottom, ResizableSplitLeft, ResizableSplitRight, ResizableSplitTop +#include "ftxui/component/component_base.hpp" // for Component, ComponentBase #include "ftxui/component/event.hpp" // for Event #include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed, Mouse::Released -#include "ftxui/dom/elements.hpp" // for operator|, reflect, Element, separator, size, EQUAL, xflex, yflex, hbox, vbox, HEIGHT, WIDTH +#include "ftxui/dom/elements.hpp" // for operator|, reflect, Element, size, EQUAL, xflex, yflex, hbox, vbox, HEIGHT, WIDTH, text #include "ftxui/screen/box.hpp" // for Box namespace ftxui { namespace { -class ResizableSplitLeftBase : public ComponentBase { +class ResizableSplitBase : public ComponentBase { public: - ResizableSplitLeftBase(Component main, Component child, int* main_size) - : main_(std::move(main)), - child_(std::move(child)), - main_size_(main_size) { + ResizableSplitBase(ResizableSplitOption options) + : options_(std::move(options)) { Add(Container::Horizontal({ - main_, - child_, + options_->main, + options_->back, })); } @@ -45,204 +47,86 @@ class ResizableSplitLeftBase : public ComponentBase { return true; } - if (captured_mouse_) { - *main_size_ = event.mouse().x - box_.x_min; - return true; + if (!captured_mouse_) { + return ComponentBase::OnEvent(event); } - return ComponentBase::OnEvent(event); + switch (options_->direction()) { + case Direction::Left: + options_->main_size() = event.mouse().x - box_.x_min; + return true; + case Direction::Right: + options_->main_size() = box_.x_max - event.mouse().x; + return true; + case Direction::Up: + options_->main_size() = event.mouse().y - box_.y_min; + return true; + case Direction::Down: + options_->main_size() = box_.y_max - event.mouse().y; + return true; + } + + // NOTREACHED() + return false; } Element Render() final { + switch (options_->direction()) { + case Direction::Left: + return RenderLeft(); + case Direction::Right: + return RenderRight(); + case Direction::Up: + return RenderTop(); + case Direction::Down: + return RenderBottom(); + } + // NOTREACHED() + return text("unreacheable"); + } + + Element RenderLeft() { return hbox({ - main_->Render() | size(WIDTH, EQUAL, *main_size_), - separator() | reflect(separator_box_), - child_->Render() | xflex, + options_->main->Render() | + size(WIDTH, EQUAL, options_->main_size()), + options_->separator_func() | reflect(separator_box_), + options_->back->Render() | xflex, }) | reflect(box_); }; - private: - Component main_; - Component child_; - int* const main_size_; - CapturedMouse captured_mouse_; - Box separator_box_; - Box box_; -}; - -class ResizableSplitRightBase : public ComponentBase { - public: - ResizableSplitRightBase(Component main, Component child, int* main_size) - : main_(std::move(main)), - child_(std::move(child)), - main_size_(main_size) { - Add(Container::Horizontal({ - child_, - main_, - })); - } - - bool OnEvent(Event event) final { - if (event.is_mouse()) { - return OnMouseEvent(std::move(event)); - } - return ComponentBase::OnEvent(std::move(event)); - } - - bool OnMouseEvent(Event event) { - if (captured_mouse_ && event.mouse().motion == Mouse::Released) { - captured_mouse_.reset(); - return true; - } - - if (event.mouse().button == Mouse::Left && - event.mouse().motion == Mouse::Pressed && - separator_box_.Contain(event.mouse().x, event.mouse().y) && - !captured_mouse_) { - captured_mouse_ = CaptureMouse(event); - return true; - } - - if (captured_mouse_) { - *main_size_ = box_.x_max - event.mouse().x; - return true; - } - - return ComponentBase::OnEvent(event); - } - - Element Render() final { + Element RenderRight() { return hbox({ - child_->Render() | xflex, - separator() | reflect(separator_box_), - main_->Render() | size(WIDTH, EQUAL, *main_size_), + options_->back->Render() | xflex, + options_->separator_func() | reflect(separator_box_), + options_->main->Render() | + size(WIDTH, EQUAL, options_->main_size()), }) | reflect(box_); }; - private: - Component main_; - Component child_; - int* const main_size_; - CapturedMouse captured_mouse_; - Box separator_box_; - Box box_; -}; - -class ResizableSplitTopBase : public ComponentBase { - public: - ResizableSplitTopBase(Component main, Component child, int* main_size) - : main_(std::move(main)), - child_(std::move(child)), - main_size_(main_size) { - Add(Container::Vertical({ - main_, - child_, - })); - } - - bool OnEvent(Event event) final { - if (event.is_mouse()) { - return OnMouseEvent(std::move(event)); - } - return ComponentBase::OnEvent(std::move(event)); - } - - bool OnMouseEvent(Event event) { - if (captured_mouse_ && event.mouse().motion == Mouse::Released) { - captured_mouse_.reset(); - return true; - } - - if (event.mouse().button == Mouse::Left && - event.mouse().motion == Mouse::Pressed && - separator_box_.Contain(event.mouse().x, event.mouse().y) && - !captured_mouse_) { - captured_mouse_ = CaptureMouse(event); - return true; - } - - if (captured_mouse_) { - *main_size_ = event.mouse().y - box_.y_min; - return true; - } - - return ComponentBase::OnEvent(event); - } - - Element Render() final { + Element RenderTop() { return vbox({ - main_->Render() | size(HEIGHT, EQUAL, *main_size_), - separator() | reflect(separator_box_), - child_->Render() | yflex, + options_->main->Render() | + size(HEIGHT, EQUAL, options_->main_size()), + options_->separator_func() | reflect(separator_box_), + options_->back->Render() | yflex, }) | reflect(box_); }; - private: - Component main_; - Component child_; - int* const main_size_; - CapturedMouse captured_mouse_; - Box separator_box_; - Box box_; -}; - -class ResizableSplitBottomBase : public ComponentBase { - public: - ResizableSplitBottomBase(Component main, Component child, int* main_size) - : main_(std::move(main)), - child_(std::move(child)), - main_size_(main_size) { - Add(Container::Vertical({ - child_, - main_, - })); - } - - bool OnEvent(Event event) final { - if (event.is_mouse()) { - return OnMouseEvent(std::move(event)); - } - return ComponentBase::OnEvent(std::move(event)); - } - - bool OnMouseEvent(Event event) { - if (captured_mouse_ && event.mouse().motion == Mouse::Released) { - captured_mouse_.reset(); - return true; - } - - if (event.mouse().button == Mouse::Left && - event.mouse().motion == Mouse::Pressed && - separator_box_.Contain(event.mouse().x, event.mouse().y) && - !captured_mouse_) { - captured_mouse_ = CaptureMouse(event); - return true; - } - - if (captured_mouse_) { - *main_size_ = box_.y_max - event.mouse().y; - return true; - } - - return ComponentBase::OnEvent(event); - } - - Element Render() final { + Element RenderBottom() { return vbox({ - child_->Render() | yflex, - separator() | reflect(separator_box_), - main_->Render() | size(HEIGHT, EQUAL, *main_size_), + options_->back->Render() | yflex, + options_->separator_func() | reflect(separator_box_), + options_->main->Render() | + size(HEIGHT, EQUAL, options_->main_size()), }) | reflect(box_); }; private: - Component main_; - Component child_; - int* const main_size_; + Ref options_; CapturedMouse captured_mouse_; Box separator_box_; Box box_; @@ -250,6 +134,35 @@ class ResizableSplitBottomBase : public ComponentBase { } // namespace +/// @brief A split in between two components. +/// @param options: all the parameters. +/// +/// ### Example +/// +/// ```cpp +/// auto left = Renderer([] { return text("Left") | center;}); +/// auto right = Renderer([] { return text("right") | center;}); +/// int left_size = 10; +/// auto component = ResizableSplit({ +/// .main = left, +/// .back = right, +/// .direction = Direction::Left, +/// .main_size = &left_size, +/// .separator_func = [] { return separatorDouble(); }, +/// }); +/// ``` +/// +/// ### Output +/// +/// ```bash +/// ║ +/// left ║ right +/// ║ +/// ``` +Component ResizableSplit(ResizableSplitOption options) { + return Make(std::move(options)); +} + /// @brief An horizontal split in between two components, configurable using the /// mouse. /// @param main The main component of size |main_size|, on the left. @@ -276,8 +189,12 @@ class ResizableSplitBottomBase : public ComponentBase { /// │ /// ``` Component ResizableSplitLeft(Component main, Component back, int* main_size) { - return Make(std::move(main), std::move(back), - main_size); + return ResizableSplit({ + std::move(main), + std::move(back), + Direction::Left, + main_size, + }); } /// @brief An horizontal split in between two components, configurable using the @@ -294,7 +211,7 @@ Component ResizableSplitLeft(Component main, Component back, int* main_size) { /// int right_size = 10; /// auto left = Renderer([] { return text("Left") | center;}); /// auto right = Renderer([] { return text("right") | center;}); -/// auto split = ResizableSplitRight(right, left, &right_size); +/// auto split = ResizableSplitRight(right, left, &right_size) /// screen.Loop(split); /// ``` /// @@ -306,8 +223,12 @@ Component ResizableSplitLeft(Component main, Component back, int* main_size) { /// │ /// ``` Component ResizableSplitRight(Component main, Component back, int* main_size) { - return Make(std::move(main), std::move(back), - main_size); + return ResizableSplit({ + std::move(main), + std::move(back), + Direction::Right, + main_size, + }); } /// @brief An vertical split in between two components, configurable using the @@ -324,7 +245,7 @@ Component ResizableSplitRight(Component main, Component back, int* main_size) { /// int top_size = 1; /// auto top = Renderer([] { return text("Top") | center;}); /// auto bottom = Renderer([] { return text("Bottom") | center;}); -/// auto split = ResizableSplitTop(top, bottom, &top_size); +/// auto split = ResizableSplitTop(top, bottom, &top_size) /// screen.Loop(split); /// ``` /// @@ -336,8 +257,12 @@ Component ResizableSplitRight(Component main, Component back, int* main_size) { /// bottom /// ``` Component ResizableSplitTop(Component main, Component back, int* main_size) { - return Make(std::move(main), std::move(back), - main_size); + return ResizableSplit({ + std::move(main), + std::move(back), + Direction::Up, + main_size, + }); } /// @brief An vertical split in between two components, configurable using the @@ -354,7 +279,7 @@ Component ResizableSplitTop(Component main, Component back, int* main_size) { /// int bottom_size = 1; /// auto top = Renderer([] { return text("Top") | center;}); /// auto bottom = Renderer([] { return text("Bottom") | center;}); -/// auto split = ResizableSplit::Bottom(bottom, top, &bottom_size); +/// auto split = ResizableSplit::Bottom(bottom, top, &bottom_size) /// screen.Loop(split); /// ``` /// @@ -366,9 +291,14 @@ Component ResizableSplitTop(Component main, Component back, int* main_size) { /// bottom /// ``` Component ResizableSplitBottom(Component main, Component back, int* main_size) { - return Make(std::move(main), std::move(back), - main_size); + return ResizableSplit({ + std::move(main), + std::move(back), + Direction::Down, + main_size, + }); } + } // namespace ftxui // Copyright 2021 Arthur Sonzogni. All rights reserved. diff --git a/src/ftxui/component/resizable_split_test.cpp b/src/ftxui/component/resizable_split_test.cpp index 3716148..347310a 100644 --- a/src/ftxui/component/resizable_split_test.cpp +++ b/src/ftxui/component/resizable_split_test.cpp @@ -1,11 +1,12 @@ -#include +#include // for AssertionResult, Message, TestPartResult, Test, EXPECT_EQ, EXPECT_TRUE, TestInfo (ptr only), TEST +#include // for Direction, Direction::Down, Direction::Left, Direction::Right, Direction::Up #include // for __shared_ptr_access, shared_ptr, allocator -#include "ftxui/component/component.hpp" // for Renderer, ResizableSplitBottom, ResizableSplitLeft, ResizableSplitRight, ResizableSplitTop +#include "ftxui/component/component.hpp" // for ResizableSplit, Renderer, ResizableSplitBottom, ResizableSplitLeft, ResizableSplitRight, ResizableSplitTop #include "ftxui/component/component_base.hpp" // for ComponentBase, Component #include "ftxui/component/event.hpp" // for Event #include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed, Mouse::Released -#include "ftxui/dom/elements.hpp" // for text, Element +#include "ftxui/dom/elements.hpp" // for Element, separatorDouble, text #include "ftxui/dom/node.hpp" // for Render #include "ftxui/screen/screen.hpp" // for Screen @@ -56,6 +57,31 @@ TEST(ResizableSplit, BasicLeft) { EXPECT_EQ(position, 10); } +TEST(ResizableSplit, BasicLeftWithCustomSeparator) { + int position = 1; + auto component = ResizableSplit({ + .main = BasicComponent(), + .back = BasicComponent(), + .direction = Direction::Left, + .main_size = &position, + .separator_func = [] { return separatorDouble(); }, + }); + auto screen = Screen(4, 4); + Render(screen, component->Render()); + EXPECT_EQ(position, 1); + EXPECT_EQ(screen.ToString(), + " ║ \r\n" + " ║ \r\n" + " ║ \r\n" + " ║ "); + EXPECT_TRUE(component->OnEvent(MousePressed(1, 1))); + EXPECT_EQ(position, 1); + EXPECT_TRUE(component->OnEvent(MousePressed(2, 1))); + EXPECT_EQ(position, 2); + EXPECT_TRUE(component->OnEvent(MouseReleased(2, 1))); + EXPECT_EQ(position, 2); +} + TEST(ResizableSplit, BasicRight) { int position = 3; auto component = @@ -71,6 +97,31 @@ TEST(ResizableSplit, BasicRight) { EXPECT_EQ(position, 9); } +TEST(ResizableSplit, BasicRightWithCustomSeparator) { + int position = 1; + auto component = ResizableSplit({ + .main = BasicComponent(), + .back = BasicComponent(), + .direction = Direction::Right, + .main_size = &position, + .separator_func = [] { return separatorDouble(); }, + }); + auto screen = Screen(4, 4); + Render(screen, component->Render()); + EXPECT_EQ(position, 1); + EXPECT_EQ(screen.ToString(), + " ║ \r\n" + " ║ \r\n" + " ║ \r\n" + " ║ "); + EXPECT_TRUE(component->OnEvent(MousePressed(2, 1))); + EXPECT_EQ(position, 1); + EXPECT_TRUE(component->OnEvent(MousePressed(1, 1))); + EXPECT_EQ(position, 2); + EXPECT_TRUE(component->OnEvent(MouseReleased(1, 1))); + EXPECT_EQ(position, 2); +} + TEST(ResizableSplit, BasicTop) { int position = 3; auto component = @@ -86,6 +137,31 @@ TEST(ResizableSplit, BasicTop) { EXPECT_EQ(position, 10); } +TEST(ResizableSplit, BasicTopWithCustomSeparator) { + int position = 1; + auto component = ResizableSplit({ + .main = BasicComponent(), + .back = BasicComponent(), + .direction = Direction::Up, + .main_size = &position, + .separator_func = [] { return separatorDouble(); }, + }); + auto screen = Screen(4, 4); + Render(screen, component->Render()); + EXPECT_EQ(position, 1); + EXPECT_EQ(screen.ToString(), + " \r\n" + "════\r\n" + " \r\n" + " "); + EXPECT_TRUE(component->OnEvent(MousePressed(1, 1))); + EXPECT_EQ(position, 1); + EXPECT_TRUE(component->OnEvent(MousePressed(1, 2))); + EXPECT_EQ(position, 2); + EXPECT_TRUE(component->OnEvent(MouseReleased(1, 2))); + EXPECT_EQ(position, 2); +} + TEST(ResizableSplit, BasicBottom) { int position = 3; auto component = @@ -101,6 +177,31 @@ TEST(ResizableSplit, BasicBottom) { EXPECT_EQ(position, 9); } +TEST(ResizableSplit, BasicBottomWithCustomSeparator) { + int position = 1; + auto component = ResizableSplit({ + .main = BasicComponent(), + .back = BasicComponent(), + .direction = Direction::Down, + .main_size = &position, + .separator_func = [] { return separatorDouble(); }, + }); + auto screen = Screen(4, 4); + Render(screen, component->Render()); + EXPECT_EQ(position, 1); + EXPECT_EQ(screen.ToString(), + " \r\n" + " \r\n" + "════\r\n" + " "); + EXPECT_TRUE(component->OnEvent(MousePressed(1, 2))); + EXPECT_EQ(position, 1); + EXPECT_TRUE(component->OnEvent(MousePressed(1, 1))); + EXPECT_EQ(position, 2); + EXPECT_TRUE(component->OnEvent(MouseReleased(1, 1))); + EXPECT_EQ(position, 2); +} + } // namespace ftxui // Copyright 2022 Arthur Sonzogni. All rights reserved. diff --git a/src/ftxui/component/slider.cpp b/src/ftxui/component/slider.cpp index e45bd91..8ae3336 100644 --- a/src/ftxui/component/slider.cpp +++ b/src/ftxui/component/slider.cpp @@ -1,7 +1,8 @@ #include // for max, min #include // for SliderOption -#include // for allocator -#include // for move +#include // for Direction, Direction::Down, Direction::Left, Direction::Right, Direction::Up +#include // for allocator +#include // for move #include "ftxui/component/captured_mouse.hpp" // for CapturedMouse #include "ftxui/component/component.hpp" // for Make, Slider @@ -9,7 +10,7 @@ #include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp #include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed, Mouse::Released #include "ftxui/component/screen_interactive.hpp" // for Component -#include "ftxui/dom/elements.hpp" // for operator|, text, GaugeDirection, Element, xflex, hbox, color, underlined, GaugeDirection::Down, GaugeDirection::Left, GaugeDirection::Right, GaugeDirection::Up, reflect, Decorator, dim, vcenter, yflex, gaugeDirection +#include "ftxui/dom/elements.hpp" // for operator|, text, Element, xflex, hbox, color, underlined, reflect, Decorator, dim, vcenter, focus, nothing, select, yflex, gaugeDirection #include "ftxui/screen/box.hpp" // for Box #include "ftxui/screen/color.hpp" // for Color, Color::GrayDark, Color::White #include "ftxui/screen/util.hpp" // for clamp @@ -18,13 +19,13 @@ namespace ftxui { namespace { -Decorator flexDirection(GaugeDirection direction) { +Decorator flexDirection(Direction direction) { switch (direction) { - case GaugeDirection::Up: - case GaugeDirection::Down: + case Direction::Up: + case Direction::Down: return yflex; - case GaugeDirection::Left: - case GaugeDirection::Right: + case Direction::Left: + case Direction::Right: return xflex; } return xflex; // NOT_REACHED() @@ -52,56 +53,56 @@ class SliderBase : public ComponentBase { void OnLeft() { switch (options_->direction) { - case GaugeDirection::Right: + case Direction::Right: value_() -= increment_(); break; - case GaugeDirection::Left: + case Direction::Left: value_() += increment_(); break; - case GaugeDirection::Up: - case GaugeDirection::Down: + case Direction::Up: + case Direction::Down: break; } } void OnRight() { switch (options_->direction) { - case GaugeDirection::Right: + case Direction::Right: value_() += increment_(); break; - case GaugeDirection::Left: + case Direction::Left: value_() -= increment_(); break; - case GaugeDirection::Up: - case GaugeDirection::Down: + case Direction::Up: + case Direction::Down: break; } } void OnUp() { switch (options_->direction) { - case GaugeDirection::Up: + case Direction::Up: value_() -= increment_(); break; - case GaugeDirection::Down: + case Direction::Down: value_() += increment_(); break; - case GaugeDirection::Left: - case GaugeDirection::Right: + case Direction::Left: + case Direction::Right: break; } } void OnDown() { switch (options_->direction) { - case GaugeDirection::Down: + case Direction::Down: value_() -= increment_(); break; - case GaugeDirection::Up: + case Direction::Up: value_() += increment_(); break; - case GaugeDirection::Left: - case GaugeDirection::Right: + case Direction::Left: + case Direction::Right: break; } } @@ -153,25 +154,25 @@ class SliderBase : public ComponentBase { if (captured_mouse_) { switch (options_->direction) { - case GaugeDirection::Right: { + case Direction::Right: { value_() = min_() + (event.mouse().x - gauge_box_.x_min) * (max_() - min_()) / (gauge_box_.x_max - gauge_box_.x_min); break; } - case GaugeDirection::Left: { + case Direction::Left: { value_() = max_() - (event.mouse().x - gauge_box_.x_min) * (max_() - min_()) / (gauge_box_.x_max - gauge_box_.x_min); break; } - case GaugeDirection::Down: { + case Direction::Down: { value_() = min_() + (event.mouse().y - gauge_box_.y_min) * (max_() - min_()) / (gauge_box_.y_max - gauge_box_.y_min); break; } - case GaugeDirection::Up: { + case Direction::Up: { value_() = max_() - (event.mouse().y - gauge_box_.y_min) * (max_() - min_()) / (gauge_box_.y_max - gauge_box_.y_min); diff --git a/src/ftxui/component/slider_test.cpp b/src/ftxui/component/slider_test.cpp index 98a8a19..b982513 100644 --- a/src/ftxui/component/slider_test.cpp +++ b/src/ftxui/component/slider_test.cpp @@ -2,7 +2,8 @@ #include // for size_t #include // for array #include // for Mouse, Mouse::Left, Mouse::Pressed, Mouse::Released -#include // for GaugeDirection, GaugeDirection::Down, GaugeDirection::Left, GaugeDirection::Right, GaugeDirection::Up, frame +#include // for Direction, Direction::Down, Direction::Left, Direction::Right, Direction::Up +#include // for frame #include // for shared_ptr, __shared_ptr_access, allocator #include // for to_string @@ -47,7 +48,7 @@ TEST(SliderTest, Right) { .min = 0, .max = 100, .increment = 10, - .direction = GaugeDirection::Right, + .direction = Direction::Right, }); Screen screen(11, 1); Render(screen, slider->Render()); @@ -70,7 +71,7 @@ TEST(SliderTest, Left) { .min = 0, .max = 100, .increment = 10, - .direction = GaugeDirection::Left, + .direction = Direction::Left, }); Screen screen(11, 1); Render(screen, slider->Render()); @@ -93,7 +94,7 @@ TEST(SliderTest, Down) { .min = 0, .max = 100, .increment = 10, - .direction = GaugeDirection::Down, + .direction = Direction::Down, }); Screen screen(1, 11); Render(screen, slider->Render()); @@ -116,7 +117,7 @@ TEST(SliderTest, Up) { .min = 0, .max = 100, .increment = 10, - .direction = GaugeDirection::Up, + .direction = Direction::Up, }); Screen screen(1, 11); Render(screen, slider->Render()); diff --git a/src/ftxui/dom/gauge.cpp b/src/ftxui/dom/gauge.cpp index 755a57f..304b9e4 100644 --- a/src/ftxui/dom/gauge.cpp +++ b/src/ftxui/dom/gauge.cpp @@ -1,8 +1,9 @@ -#include // for allocator, make_shared -#include // for string +#include // for Direction, Direction::Down, Direction::Left, Direction::Right, Direction::Up +#include // for allocator, make_shared +#include // for string -#include "ftxui/dom/elements.hpp" // for GaugeDirection, Element, GaugeDirection::Down, GaugeDirection::Left, GaugeDirection::Right, GaugeDirection::Up, gauge, gaugeDirection, gaugeDown, gaugeLeft, gaugeRight, gaugeUp -#include "ftxui/dom/node.hpp" // for Node +#include "ftxui/dom/elements.hpp" // for Element, gauge, gaugeDirection, gaugeDown, gaugeLeft, gaugeRight, gaugeUp +#include "ftxui/dom/node.hpp" // for Node #include "ftxui/dom/requirement.hpp" // for Requirement #include "ftxui/screen/box.hpp" // for Box #include "ftxui/screen/screen.hpp" // for Screen, Pixel @@ -40,7 +41,7 @@ static const std::string charset_vertical[10] = { class Gauge : public Node { public: - Gauge(float progress, GaugeDirection direction) + Gauge(float progress, Direction direction) : progress_(progress), direction_(direction) { // This handle NAN correctly: if (!(progress_ > 0.F)) { @@ -53,15 +54,15 @@ class Gauge : public Node { void ComputeRequirement() override { switch (direction_) { - case GaugeDirection::Right: - case GaugeDirection::Left: + case Direction::Right: + case Direction::Left: requirement_.flex_grow_x = 1; requirement_.flex_grow_y = 0; requirement_.flex_shrink_x = 1; requirement_.flex_shrink_y = 0; break; - case GaugeDirection::Up: - case GaugeDirection::Down: + case Direction::Up: + case Direction::Down: requirement_.flex_grow_x = 0; requirement_.flex_grow_y = 1; requirement_.flex_shrink_x = 0; @@ -74,16 +75,16 @@ class Gauge : public Node { void Render(Screen& screen) override { switch (direction_) { - case GaugeDirection::Right: + case Direction::Right: RenderHorizontal(screen, /*invert=*/false); break; - case GaugeDirection::Up: + case Direction::Up: RenderVertical(screen, /*invert=*/false); break; - case GaugeDirection::Left: + case Direction::Left: RenderHorizontal(screen, /*invert=*/true); break; - case GaugeDirection::Down: + case Direction::Down: RenderVertical(screen, /*invert=*/true); break; } @@ -151,7 +152,7 @@ class Gauge : public Node { private: float progress_; - GaugeDirection direction_; + Direction direction_; }; /// @brief Draw a high definition progress bar progressing in specified @@ -159,7 +160,7 @@ class Gauge : public Node { /// @param progress The proportion of the area to be filled. Belong to [0,1]. // @param direction Direction of progress bars progression. /// @ingroup dom -Element gaugeDirection(float progress, GaugeDirection direction) { +Element gaugeDirection(float progress, Direction direction) { return std::make_shared(progress, direction); } @@ -182,7 +183,7 @@ Element gaugeDirection(float progress, GaugeDirection direction) { /// └──────────────────────────────────────────────────────────────────────────┘ /// ~~~ Element gaugeRight(float progress) { - return gaugeDirection(progress, GaugeDirection::Right); + return gaugeDirection(progress, Direction::Right); } /// @brief Draw a high definition progress bar progressing from right to left. @@ -204,7 +205,7 @@ Element gaugeRight(float progress) { /// └──────────────────────────────────────────────────────────────────────────┘ /// ~~~ Element gaugeLeft(float progress) { - return gaugeDirection(progress, GaugeDirection::Left); + return gaugeDirection(progress, Direction::Left); } /// @brief Draw a high definition progress bar progressing from bottom to top. @@ -233,7 +234,7 @@ Element gaugeLeft(float progress) { /// └─┘ /// ~~~ Element gaugeUp(float progress) { - return gaugeDirection(progress, GaugeDirection::Up); + return gaugeDirection(progress, Direction::Up); } /// @brief Draw a high definition progress bar progressing from top to bottom. @@ -262,7 +263,7 @@ Element gaugeUp(float progress) { /// └─┘ /// ~~~ Element gaugeDown(float progress) { - return gaugeDirection(progress, GaugeDirection::Down); + return gaugeDirection(progress, Direction::Down); } /// @brief Draw a high definition progress bar. diff --git a/src/ftxui/dom/size.cpp b/src/ftxui/dom/size.cpp index bef4ebe..750ba8c 100644 --- a/src/ftxui/dom/size.cpp +++ b/src/ftxui/dom/size.cpp @@ -3,7 +3,7 @@ #include // for move #include // for __alloc_traits<>::value_type -#include "ftxui/dom/elements.hpp" // for Constraint, Direction, EQUAL, GREATER_THAN, LESS_THAN, WIDTH, unpack, Decorator, Element, size +#include "ftxui/dom/elements.hpp" // for Constraint, WidthOrHeight, EQUAL, GREATER_THAN, LESS_THAN, WIDTH, unpack, Decorator, Element, size #include "ftxui/dom/node.hpp" // for Node, Elements #include "ftxui/dom/requirement.hpp" // for Requirement #include "ftxui/screen/box.hpp" // for Box @@ -12,7 +12,7 @@ namespace ftxui { class Size : public Node { public: - Size(Element child, Direction direction, Constraint constraint, int value) + Size(Element child, WidthOrHeight direction, Constraint constraint, int value) : Node(unpack(std::move(child))), direction_(direction), constraint_(constraint), @@ -71,7 +71,7 @@ class Size : public Node { } private: - Direction direction_; + WidthOrHeight direction_; Constraint constraint_; int value_; }; @@ -82,7 +82,7 @@ class Size : public Node { /// @param constraint The type of constaint. /// @param value The value. /// @ingroup dom -Decorator size(Direction direction, Constraint constraint, int value) { +Decorator size(WidthOrHeight direction, Constraint constraint, int value) { return [=](Element e) { return std::make_shared(std::move(e), direction, constraint, value); };