From 026a00575399595759e419085b0e18b4734dc4cb Mon Sep 17 00:00:00 2001 From: Arthur Sonzogni Date: Fri, 15 Oct 2021 23:04:11 +0200 Subject: [PATCH] Table (#229) This class allows rendering stylized table. --- CHANGELOG.md | 10 +- CMakeLists.txt | 1 + cmake/ftxui_test.cmake | 1 + examples/component/dropdown.cpp | 29 +- examples/component/maybe.cpp | 17 +- examples/dom/CMakeLists.txt | 9 +- examples/dom/table.cpp | 66 +++ include/ftxui/component/event.hpp | 1 + include/ftxui/dom/elements.hpp | 6 +- include/ftxui/dom/table.hpp | 93 ++++ src/ftxui/component/dropdown.cpp | 16 +- src/ftxui/component/maybe.cpp | 19 +- src/ftxui/component/show.cpp | 4 + src/ftxui/dom/border.cpp | 72 ++- src/ftxui/dom/composite_decorator.cpp | 8 +- src/ftxui/dom/scroll_indicator.cpp | 19 +- src/ftxui/dom/separator.cpp | 231 +++++++-- src/ftxui/dom/table.cpp | 295 +++++++++++ src/ftxui/dom/table_test.cpp | 716 ++++++++++++++++++++++++++ src/ftxui/dom/util.cpp | 12 + src/ftxui/screen/screen.cpp | 1 - 21 files changed, 1507 insertions(+), 119 deletions(-) create mode 100644 examples/dom/table.cpp create mode 100644 include/ftxui/dom/table.hpp create mode 100644 src/ftxui/dom/table.cpp create mode 100644 src/ftxui/dom/table_test.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index d805710b9..fd47c9458 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,11 +19,15 @@ Added: - Fix the automated merge of borders. ### Dom - - `vscroll_indicator`. Show a scrollbar indicator on the right. +- `Table()` class to build stylised table. + See https://github.com/ArthurSonzogni/FTXUI/discussions/228 +- `vscroll_indicator`. Show a scrollbar indicator on the right. +- `separatorEmpty`. A separator drawing nothing. +- `separatorFixed`. A separator drawing the provided character. ### Component - - `Maybe`: Display an component conditionnally based on a boolean. - - `Dropdown`: A dropdown select list. +- `Maybe`: Display an component conditionnally based on a boolean. +- `Dropdown`: A dropdown select list. 0.9 (2021-09-26) ---------------- diff --git a/CMakeLists.txt b/CMakeLists.txt index 092894fb0..be086d8ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,6 +69,7 @@ add_library(dom src/ftxui/dom/separator.cpp src/ftxui/dom/size.cpp src/ftxui/dom/spinner.cpp + src/ftxui/dom/table.cpp src/ftxui/dom/text.cpp src/ftxui/dom/underlined.cpp src/ftxui/dom/util.cpp diff --git a/cmake/ftxui_test.cmake b/cmake/ftxui_test.cmake index cab0b97cf..4f3c1b263 100644 --- a/cmake/ftxui_test.cmake +++ b/cmake/ftxui_test.cmake @@ -20,6 +20,7 @@ add_executable(tests src/ftxui/component/terminal_input_parser_test.cpp src/ftxui/component/toggle_test.cpp src/ftxui/dom/gauge_test.cpp + src/ftxui/dom/table_test.cpp src/ftxui/dom/gridbox_test.cpp src/ftxui/dom/hbox_test.cpp src/ftxui/dom/text_test.cpp diff --git a/examples/component/dropdown.cpp b/examples/component/dropdown.cpp index 393ce1cc4..6c8f397d7 100644 --- a/examples/component/dropdown.cpp +++ b/examples/component/dropdown.cpp @@ -1,26 +1,23 @@ -#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 // for basic_string, 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/captured_mouse.hpp" // for ftxui +#include "ftxui/component/component.hpp" // for Dropdown, Horizontal, Vertical #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", + "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; diff --git a/examples/component/maybe.cpp b/examples/component/maybe.cpp index 291505b58..1d4d8910c 100644 --- a/examples/component/maybe.cpp +++ b/examples/component/maybe.cpp @@ -1,12 +1,12 @@ -#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 // for shared_ptr, __shared_ptr_access +#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 +#include "ftxui/component/captured_mouse.hpp" // for ftxui +#include "ftxui/component/component.hpp" // for Checkbox, Maybe, Radiobox, Renderer, Vertical +#include "ftxui/component/component_base.hpp" // for ComponentBase +#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive +#include "ftxui/dom/elements.hpp" // for Element, operator|, border using namespace ftxui; Component Border(Component child) { @@ -14,7 +14,6 @@ Component Border(Component child) { } int main(int argc, const char* argv[]) { - std::vector entries = { "entry 1", "entry 2", diff --git a/examples/dom/CMakeLists.txt b/examples/dom/CMakeLists.txt index 758e35b08..9508f5dbc 100644 --- a/examples/dom/CMakeLists.txt +++ b/examples/dom/CMakeLists.txt @@ -3,9 +3,13 @@ set(DIRECTORY_LIB dom) example(border) example(border_style) example(color_gallery) +example(color_info_palette256) +example(color_truecolor_HSV) +example(color_truecolor_RGB) example(dbox) example(gauge) example(graph) +example(gridbox) example(hflow) example(html_like) example(package_manager) @@ -17,13 +21,10 @@ example(spinner) example(style_blink) example(style_bold) example(style_color) -example(color_truecolor_RGB) -example(color_truecolor_HSV) -example(color_info_palette256) example(style_dim) -example(gridbox) example(style_gallery) example(style_inverted) example(style_underlined) +example(table) example(vbox_hbox) example(window) diff --git a/examples/dom/table.cpp b/examples/dom/table.cpp new file mode 100644 index 000000000..cb78d51f6 --- /dev/null +++ b/examples/dom/table.cpp @@ -0,0 +1,66 @@ +#include // for color, Fit, LIGHT, align_right, bold, DOUBLE +#include // for Table, TableSelection +#include // for Screen +#include // for endl, cout, ostream +#include // for basic_string, allocator, string +#include // for vector + +#include "ftxui/dom/node.hpp" // for Render +#include "ftxui/screen/box.hpp" // for ftxui +#include "ftxui/screen/color.hpp" // for Color, Color::Blue, Color::Cyan, Color::White + +int main(int argc, const char* argv[]) { + using namespace ftxui; + + auto table = Table({ + {"Version", "Marketing name", "Release date", "API level", "Runtime"}, + {"2.3", "Gingerbread", "February 9 2011", "10", "Dalvik 1.4.0"}, + {"4.0", "Ice Cream Sandwich", "October 19 2011", "15", "Dalvik"}, + {"4.1", "Jelly Bean", "July 9 2012", "16", "Dalvik"}, + {"4.2", "Jelly Bean", "November 13 2012", "17", "Dalvik"}, + {"4.3", "Jelly Bean", "July 24 2013", "18", "Dalvik"}, + {"4.4", "KitKat", "October 31 2013", "19", "Dalvik and ART"}, + {"5.0", "Lollipop", "November 3 2014", "21", "ART"}, + {"5.1", "Lollipop", "March 9 2015", "22", "ART"}, + {"6.0", "Marshmallow", "October 5 2015", "23", "ART"}, + {"7.0", "Nougat", "August 22 2016", "24", "ART"}, + {"7.1", "Nougat", "October 4 2016", "25", "ART"}, + {"8.0", "Oreo", "August 21 2017", "26", "ART"}, + {"8.1", "Oreo", "December 5 2017", "27", "ART"}, + {"9", "Pie", "August 6 2018", "28", "ART"}, + {"10", "10", "September 3 2019", "29", "ART"}, + {"11", "11", "September 8 2020", "30", "ART"}, + }); + + table.SelectAll().Border(LIGHT); + + // Add border around the first column. + table.SelectColumn(0).Border(LIGHT); + + // Make first row bold with a double border. + table.SelectRow(0).Decorate(bold); + table.SelectRow(0).SeparatorVertical(LIGHT); + table.SelectRow(0).Border(DOUBLE); + + // Align right the "Release date" column. + table.SelectColumn(2).DecorateCells(align_right); + + // Select row from the second to the last. + auto content = table.SelectRows(1, -1); + // Alternate in between 3 colors. + content.DecorateCellsAlternateRow(color(Color::Blue), 3, 0); + content.DecorateCellsAlternateRow(color(Color::Cyan), 3, 1); + content.DecorateCellsAlternateRow(color(Color::White), 3, 2); + + auto document = table.Render(); + auto screen = Screen::Create(Dimension::Fit(document)); + Render(screen, document); + screen.Print(); + std::cout << std::endl; + + return 0; +} + +// 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/event.hpp b/include/ftxui/component/event.hpp index 7d8b2a3b1..56e73a715 100644 --- a/include/ftxui/component/event.hpp +++ b/include/ftxui/component/event.hpp @@ -75,6 +75,7 @@ struct Event { //--- State section ---------------------------------------------------------- ScreenInteractive* screen_ = nullptr; + private: friend ComponentBase; friend ScreenInteractive; diff --git a/include/ftxui/dom/elements.hpp b/include/ftxui/dom/elements.hpp index 6d86df8f5..0a77850ae 100644 --- a/include/ftxui/dom/elements.hpp +++ b/include/ftxui/dom/elements.hpp @@ -17,7 +17,7 @@ using Elements = std::vector; using Decorator = std::function; using GraphFunction = std::function(int, int)>; -enum BorderStyle { LIGHT, HEAVY, DOUBLE, ROUNDED }; +enum BorderStyle { LIGHT, HEAVY, DOUBLE, ROUNDED, EMPTY }; // Pipe elements into decorator togethers. // For instance the next lines are equivalents: @@ -34,20 +34,24 @@ Element separator(void); Element separatorLight(); Element separatorHeavy(); Element separatorDouble(); +Element separatorEmpty(); Element separatorStyled(BorderStyle); Element separator(Pixel); +Element separatorCharacter(std::string); Element gauge(float ratio); Element border(Element); Element borderLight(Element); Element borderHeavy(Element); Element borderDouble(Element); Element borderRounded(Element); +Element borderEmpty(Element); Decorator borderStyled(BorderStyle); Decorator borderWith(Pixel); Element window(Element title, Element content); Element spinner(int charset_index, size_t image_index); Elements paragraph(std::string text); // Use inside hflow(). Split by space. Element graph(GraphFunction); +Element emptyElement(); // -- Decorator --- Element bold(Element); diff --git a/include/ftxui/dom/table.hpp b/include/ftxui/dom/table.hpp new file mode 100644 index 000000000..e4a4a4147 --- /dev/null +++ b/include/ftxui/dom/table.hpp @@ -0,0 +1,93 @@ +#ifndef FTXUI_DOM_TABLE +#define FTXUI_DOM_TABLE + +#include +#include // for string +#include // for vector + +#include "ftxui/dom/elements.hpp" // for BorderStyle, LIGHT, Element, Decorator + +namespace ftxui { + +// Usage: +// +// Initialization: +// --------------- +// +// auto table = Table({ +// {"X", "Y"}, +// {"-1", "1"}, +// {"+0", "0"}, +// {"+1", "1"}, +// }); +// +// table.SelectAll().Border(LIGHT); +// +// table.SelectRow(1).Border(DOUBLE); +// table.SelectRow(1).SeparatorInternal(Light); +// +// std::move(table).Element(); + +class Table; +class TableSelection; + +class Table { + public: + Table(std::vector>); + TableSelection SelectAll(); + TableSelection SelectCell(int column, int row); + TableSelection SelectRow(int row_index); + TableSelection SelectRows(int row_min, int row_max); + TableSelection SelectColumn(int column_index); + TableSelection SelectColumns(int column_min, int column_max); + TableSelection SelectRectangle(int column_min, + int column_max, + int row_min, + int row_max); + Element Render(); + + private: + friend TableSelection; + std::vector> elements_; + int input_dim_x_; + int input_dim_y_; + int dim_x_; + int dim_y_; +}; + +class TableSelection { + public: + void Decorate(Decorator); + void DecorateAlternateRow(Decorator, int modulo = 2, int shift = 0); + void DecorateAlternateColumn(Decorator, int modulo = 2, int shift = 0); + + void DecorateCells(Decorator); + void DecorateCellsAlternateColumn(Decorator, int modulo = 2, int shift = 0); + void DecorateCellsAlternateRow(Decorator, int modulo = 2, int shift = 0); + + void Border(BorderStyle border = LIGHT); + void BorderLeft(BorderStyle border = LIGHT); + void BorderRight(BorderStyle border = LIGHT); + void BorderTop(BorderStyle border = LIGHT); + void BorderBottom(BorderStyle border = LIGHT); + + void Separator(BorderStyle border = LIGHT); + void SeparatorVertical(BorderStyle border = LIGHT); + void SeparatorHorizontal(BorderStyle border = LIGHT); + + private: + friend Table; + Table* table_; + int x_min_; + int x_max_; + int y_min_; + int y_max_; +}; + +} // namespace ftxui + +#endif /* end of include guard: FTXUI_DOM_TABLE */ + +// Copyright 2021 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/src/ftxui/component/dropdown.cpp b/src/ftxui/component/dropdown.cpp index 839cb6468..5886dc10e 100644 --- a/src/ftxui/component/dropdown.cpp +++ b/src/ftxui/component/dropdown.cpp @@ -1,6 +1,12 @@ -#include "ftxui/component/component.hpp" -#include "ftxui/component/component_base.hpp" -#include "ftxui/component/event.hpp" +#include // for __shared_ptr_access +#include // for string +#include // for move + +#include "ftxui/component/component.hpp" // for Maybe, Checkbox, Make, Radiobox, Vertical, Dropdown +#include "ftxui/component/component_base.hpp" // for Component, ComponentBase +#include "ftxui/component/component_options.hpp" // for CheckboxOption +#include "ftxui/dom/elements.hpp" // for operator|, Element, border, filler, separator, size, vbox, frame, vscroll_indicator, HEIGHT, LESS_THAN +#include "ftxui/util/ref.hpp" // for ConstStringListRef namespace ftxui { @@ -52,3 +58,7 @@ Component Dropdown(ConstStringListRef entries, int* selected) { } } // namespace ftxui + +// Copyright 2021 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/src/ftxui/component/maybe.cpp b/src/ftxui/component/maybe.cpp index c1048a0a4..ed8f02929 100644 --- a/src/ftxui/component/maybe.cpp +++ b/src/ftxui/component/maybe.cpp @@ -1,13 +1,18 @@ -#include "ftxui/component/component.hpp" -#include "ftxui/component/component_base.hpp" -#include "ftxui/component/event.hpp" +#include // for make_unique, __shared_ptr_access, __shared_ptr_access<>::element_type, shared_ptr +#include // for move + +#include "ftxui/component/component.hpp" // for Make, Maybe +#include "ftxui/component/component_base.hpp" // for ComponentBase, Component +#include "ftxui/component/event.hpp" // for Event +#include "ftxui/dom/elements.hpp" // for Element +#include "ftxui/dom/node.hpp" // for Node namespace ftxui { Component Maybe(Component child, bool* show) { class Impl : public ComponentBase { public: - Impl(bool* show): show_(show) {} + Impl(bool* show) : show_(show) {} private: Element Render() override { @@ -22,10 +27,14 @@ Component Maybe(Component child, bool* show) { bool* show_; }; - + auto maybe = Make(show); maybe->Add(std::move(child)); return maybe; } } // namespace ftxui + +// Copyright 2021 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/src/ftxui/component/show.cpp b/src/ftxui/component/show.cpp index 15b838803..45b026964 100644 --- a/src/ftxui/component/show.cpp +++ b/src/ftxui/component/show.cpp @@ -24,3 +24,7 @@ Component Maybe(Component child, bool* show) { }; return Make(std::move(child), show); } + +// Copyright 2021 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/src/ftxui/dom/border.cpp b/src/ftxui/dom/border.cpp index 4d11f6540..82ff6d76c 100644 --- a/src/ftxui/dom/border.cpp +++ b/src/ftxui/dom/border.cpp @@ -14,10 +14,11 @@ namespace ftxui { static std::string simple_border_charset[6][6] = { - {"┌", "┐", "└", "┘", "─", "│"}, - {"┏", "┓", "┗", "┛", "━", "┃"}, - {"╔", "╗", "╚", "╝", "═", "║"}, - {"╭", "╮", "╰", "╯", "─", "│"}, + {"┌", "┐", "└", "┘", "─", "│"}, // + {"┏", "┓", "┗", "┛", "━", "┃"}, // + {"╔", "╗", "╚", "╝", "═", "║"}, // + {"╭", "╮", "╰", "╯", "─", "│"}, // + {" ", " ", " ", " ", " ", " "}, // }; // For reference, here is the charset for normal border: @@ -124,6 +125,7 @@ class Border : public Node { /// @see borderLight /// @see borderDouble /// @see borderHeavy +/// @see borderEmpty /// @see borderRounded /// /// Add a border around an element @@ -174,6 +176,7 @@ Decorator borderStyled(BorderStyle style) { /// @see borderDouble /// @see borderHeavy /// @see borderRounded +/// @see borderEmpty /// @see borderStyled /// @see borderWith /// @@ -192,9 +195,9 @@ Decorator borderStyled(BorderStyle style) { /// ### Output /// /// ```bash -/// ┌──────────────┐ -/// │The element │ -/// └──────────────┘ +/// ┌──────────────┐ +/// │The element │ +/// └──────────────┘ /// ``` Element borderLight(Element child) { return std::make_shared(unpack(std::move(child)), LIGHT); @@ -207,6 +210,7 @@ Element borderLight(Element child) { /// @see borderDouble /// @see borderHeavy /// @see borderRounded +/// @see borderEmpty /// @see borderStyled /// @see borderWith /// @@ -225,9 +229,9 @@ Element borderLight(Element child) { /// ### Output /// /// ```bash -/// ┏━━━━━━━━━━━━━━┓ -/// ┃The element ┃ -/// ┗━━━━━━━━━━━━━━┛ +/// ┏━━━━━━━━━━━━━━┓ +/// ┃The element ┃ +/// ┗━━━━━━━━━━━━━━┛ /// ``` Element borderHeavy(Element child) { return std::make_shared(unpack(std::move(child)), HEAVY); @@ -240,6 +244,7 @@ Element borderHeavy(Element child) { /// @see borderDouble /// @see borderHeavy /// @see borderRounded +/// @see borderEmpty /// @see borderStyled /// @see borderWith /// @@ -258,9 +263,9 @@ Element borderHeavy(Element child) { /// ### Output /// /// ```bash -/// ╔══════════════╗ -/// ║The element ║ -/// ╚══════════════╝ +/// ╔══════════════╗ +/// ║The element ║ +/// ╚══════════════╝ /// ``` Element borderDouble(Element child) { return std::make_shared(unpack(std::move(child)), DOUBLE); @@ -273,6 +278,7 @@ Element borderDouble(Element child) { /// @see borderDouble /// @see borderHeavy /// @see borderRounded +/// @see borderEmpty /// @see borderStyled /// @see borderWith /// @@ -291,14 +297,48 @@ Element borderDouble(Element child) { /// ### Output /// /// ```bash -/// ╭──────────────╮ -/// │The element │ -/// ╰──────────────╯ +/// ╭──────────────╮ +/// │The element │ +/// ╰──────────────╯ /// ``` Element borderRounded(Element child) { return std::make_shared(unpack(std::move(child)), ROUNDED); } +/// @brief Draw an empty border around the element. +/// @ingroup dom +/// @see border +/// @see borderLight +/// @see borderDouble +/// @see borderHeavy +/// @see borderRounded +/// @see borderEmpty +/// @see borderStyled +/// @see borderWith +/// +/// Add a border around an element +/// +/// ### Example +/// +/// ```cpp +/// // Use 'borderRounded' as a function... +/// Element document = borderRounded(text("The element")); +/// +/// // ...Or as a 'pipe'. +/// Element document = text("The element") | borderRounded; +/// ``` +/// +/// ### Output +/// +/// ```bash +/// +/// The element +/// +/// ``` +Element borderEmpty(Element child) { + return std::make_shared(unpack(std::move(child)), EMPTY); +} + /// @brief Draw window with a title and a border around the element. /// @param title The title of the window. /// @param content The element to be wrapped. diff --git a/src/ftxui/dom/composite_decorator.cpp b/src/ftxui/dom/composite_decorator.cpp index dbfc182e0..43c4f488d 100644 --- a/src/ftxui/dom/composite_decorator.cpp +++ b/src/ftxui/dom/composite_decorator.cpp @@ -10,7 +10,7 @@ namespace ftxui { /// @return The centered element. /// @ingroup dom Element hcenter(Element child) { - return hbox(filler(), std::move(child), filler()) | xflex_grow; + return hbox(filler(), std::move(child), filler()); } /// @brief Center an element vertically. @@ -18,7 +18,7 @@ Element hcenter(Element child) { /// @return The centered element. /// @ingroup dom Element vcenter(Element child) { - return vbox(filler(), std::move(child), filler()) | yflex_grow; + return vbox(filler(), std::move(child), filler()); } /// @brief Center an element horizontally and vertically. @@ -26,7 +26,7 @@ Element vcenter(Element child) { /// @return The centered element. /// @ingroup dom Element center(Element child) { - return hcenter(vcenter(std::move(child))) | flex_grow; + return hcenter(vcenter(std::move(child))); } /// @brief Align an element on the right side. @@ -34,7 +34,7 @@ Element center(Element child) { /// @return The right aligned element. /// @ingroup dom Element align_right(Element child) { - return hbox(filler(), std::move(child)) | flex_grow; + return hbox(filler(), std::move(child)); } } // namespace ftxui diff --git a/src/ftxui/dom/scroll_indicator.cpp b/src/ftxui/dom/scroll_indicator.cpp index 2167d3ae6..ec07467ee 100644 --- a/src/ftxui/dom/scroll_indicator.cpp +++ b/src/ftxui/dom/scroll_indicator.cpp @@ -1,10 +1,15 @@ -#include // For std::max +#include // for max +#include // for make_shared, __shared_ptr_access +#include // for string +#include // for move +#include // for __alloc_traits<>::value_type -#include "ftxui/dom/elements.hpp" -#include "ftxui/dom/node.hpp" +#include "ftxui/dom/elements.hpp" // for Element, vscroll_indicator +#include "ftxui/dom/node.hpp" // for Node, Elements #include "ftxui/dom/node_decorator.hpp" // for NodeDecorator -#include "ftxui/screen/box.hpp" -#include "ftxui/screen/screen.hpp" +#include "ftxui/dom/requirement.hpp" // for Requirement +#include "ftxui/screen/box.hpp" // for Box +#include "ftxui/screen/screen.hpp" // for Screen, Pixel namespace ftxui { @@ -58,3 +63,7 @@ Element vscroll_indicator(Element child) { } } // namespace ftxui + +// Copyright 2021 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/src/ftxui/dom/separator.cpp b/src/ftxui/dom/separator.cpp index fdd0c2ece..74b451119 100644 --- a/src/ftxui/dom/separator.cpp +++ b/src/ftxui/dom/separator.cpp @@ -12,15 +12,36 @@ namespace ftxui { using ftxui::Screen; const std::string charset[][2] = { - {"│", "─"}, - {"┃", "━"}, - {"║", "═"}, - {"│", "─"}, + {"│", "─"}, // + {"┃", "━"}, // + {"║", "═"}, // + {"│", "─"}, // + {" ", " "}, // }; class Separator : public Node { public: - Separator(BorderStyle style) : style_(style) {} + Separator(std::string value) : value_(value) {} + + void ComputeRequirement() override { + requirement_.min_x = 1; + requirement_.min_y = 1; + } + + void Render(Screen& screen) override { + for (int y = box_.y_min; y <= box_.y_max; ++y) { + for (int x = box_.x_min; x <= box_.x_max; ++x) { + screen.PixelAt(x, y).character = value_; + } + } + } + + std::string value_; +}; + +class SeparatorAuto : public Node { + public: + SeparatorAuto(BorderStyle style) : style_(style) {} void ComputeRequirement() override { requirement_.min_x = 1; @@ -43,9 +64,9 @@ class Separator : public Node { BorderStyle style_; }; -class SeparatorWithPixel : public Separator { +class SeparatorWithPixel : public SeparatorAuto { public: - SeparatorWithPixel(Pixel pixel) : Separator(LIGHT), pixel_(pixel) {} + SeparatorWithPixel(Pixel pixel) : SeparatorAuto(LIGHT), pixel_(pixel) {} void Render(Screen& screen) override { for (int y = box_.y_min; y <= box_.y_max; ++y) { for (int x = box_.x_min; x <= box_.x_max; ++x) { @@ -58,152 +79,258 @@ class SeparatorWithPixel : public Separator { Pixel pixel_; }; -/// @brief Draw a vertical or horizontal separator in between two elements. +/// @brief Draw a vertical or horizontal separation in between two other +/// elements. /// @ingroup dom /// @see separator /// @see separatorLight -/// @see separatorHeavy /// @see separatorDouble +/// @see separatorHeavy +/// @see separatorEmpty +/// @see separatorRounded /// @see separatorStyled +/// @see separatorCharacter +/// +/// Add a visual separation in between two elements. /// /// ### Example /// /// ```cpp +/// // Use 'border' as a function... /// Element document = vbox({ -/// text("Up"), +/// text("up"), /// separator(), -/// text("Down"), -/// }) +/// text("down"), +/// }); /// ``` /// /// ### Output /// /// ```bash -/// Up +/// up /// ──── -/// Down +/// down /// ``` Element separator() { - return std::make_shared(LIGHT); + return std::make_shared(LIGHT); } -/// @brief Draw a vertical or horizontal separator in between two elements. +/// @brief Draw a vertical or horizontal separation in between two other +/// elements. +/// @param style the style of the separator. /// @ingroup dom /// @see separator /// @see separatorLight -/// @see separatorHeavy /// @see separatorDouble +/// @see separatorHeavy +/// @see separatorEmpty +/// @see separatorRounded /// @see separatorStyled +/// @see separatorCharacter +/// +/// Add a visual separation in between two elements. /// /// ### Example /// /// ```cpp +/// // Use 'border' as a function... /// Element document = vbox({ -/// text("Up"), -/// separatorStyled(BorderStyle::LIGHT), -/// text("Down"), -/// }) +/// text("up"), +/// separatorStyled(DOUBLE), +/// text("down"), +/// }); /// ``` /// /// ### Output /// /// ```bash -/// Up -/// ──── -/// Down +/// up +/// ════ +/// down /// ``` Element separatorStyled(BorderStyle style) { - return std::make_shared(style); + return std::make_shared(style); } -/// @brief Draw a vertical or horizontal light separator in between two -/// elements. +/// @brief Draw a vertical or horizontal separation in between two other +/// elements, using the LIGHT style. /// @ingroup dom /// @see separator /// @see separatorLight -/// @see separatorHeavy /// @see separatorDouble +/// @see separatorHeavy +/// @see separatorEmpty +/// @see separatorRounded /// @see separatorStyled +/// @see separatorCharacter +/// +/// Add a visual separation in between two elements. /// /// ### Example /// /// ```cpp +/// // Use 'border' as a function... /// Element document = vbox({ -/// text("Up"), +/// text("up"), /// separatorLight(), -/// text("Down"), -/// }) +/// text("down"), +/// }); /// ``` /// /// ### Output /// /// ```bash -/// Up +/// up /// ──── -/// Down +/// down /// ``` Element separatorLight() { - return std::make_shared(LIGHT); + return std::make_shared(LIGHT); } -/// @brief Draw a vertical or horizontal heavy separator in between two -/// elements. +/// @brief Draw a vertical or horizontal separation in between two other +/// elements, using the HEAVY style. /// @ingroup dom /// @see separator /// @see separatorLight -/// @see separatorHeavy /// @see separatorDouble +/// @see separatorHeavy +/// @see separatorEmpty +/// @see separatorRounded /// @see separatorStyled +/// @see separatorCharacter +/// +/// Add a visual separation in between two elements. /// /// ### Example /// /// ```cpp +/// // Use 'border' as a function... /// Element document = vbox({ -/// text("Up"), +/// text("up"), /// separatorHeavy(), -/// text("Down"), -/// }) +/// text("down"), +/// }); /// ``` /// /// ### Output /// /// ```bash -/// Up +/// up /// ━━━━ -/// Down +/// down /// ``` Element separatorHeavy() { - return std::make_shared(HEAVY); + return std::make_shared(HEAVY); } -/// @brief Draw a vertical or horizontal double separator in between two -/// elements. +/// @brief Draw a vertical or horizontal separation in between two other +/// elements, using the DOUBLE style. /// @ingroup dom /// @see separator /// @see separatorLight -/// @see separatorHeavy /// @see separatorDouble +/// @see separatorHeavy +/// @see separatorEmpty +/// @see separatorRounded /// @see separatorStyled +/// @see separatorCharacter +/// +/// Add a visual separation in between two elements. /// /// ### Example /// /// ```cpp +/// // Use 'border' as a function... /// Element document = vbox({ -/// text("Up"), +/// text("up"), /// separatorDouble(), -/// text("Down"), -/// }) +/// text("down"), +/// }); /// ``` /// /// ### Output /// /// ```bash -/// Up +/// up /// ════ -/// Down +/// down /// ``` Element separatorDouble() { - return std::make_shared(DOUBLE); + return std::make_shared(DOUBLE); +} + +/// @brief Draw a vertical or horizontal separation in between two other +/// elements, using the EMPTY style. +/// @ingroup dom +/// @see separator +/// @see separatorLight +/// @see separatorDouble +/// @see separatorHeavy +/// @see separatorEmpty +/// @see separatorRounded +/// @see separatorStyled +/// @see separatorCharacter +/// +/// Add a visual separation in between two elements. +/// +/// ### Example +/// +/// ```cpp +/// // Use 'border' as a function... +/// Element document = vbox({ +/// text("up"), +/// separator(), +/// text("down"), +/// }); +/// ``` +/// +/// ### Output +/// +/// ```bash +/// up +/// +/// down +/// ``` +Element separatorEmpty() { + return std::make_shared(EMPTY); +} + +/// @brief Draw a vertical or horizontal separation in between two other +/// elements. +/// @param value the character to fill the separator area. +/// @ingroup dom +/// @see separator +/// @see separatorLight +/// @see separatorDouble +/// @see separatorHeavy +/// @see separatorEmpty +/// @see separatorRounded +/// @see separatorStyled +/// @see separatorCharacter +/// +/// Add a visual separation in between two elements. +/// +/// ### Example +/// +/// ```cpp +/// // Use 'border' as a function... +/// Element document = vbox({ +/// text("up"), +/// separator(), +/// text("down"), +/// }); +/// ``` +/// +/// ### Output +/// +/// ```bash +/// up +/// ──── +/// down +/// ``` +Element separatorCharacter(std::string value) { + return std::make_shared(value); } /// @brief Draw a separator in between two element filled with a given pixel. diff --git a/src/ftxui/dom/table.cpp b/src/ftxui/dom/table.cpp new file mode 100644 index 000000000..5a93445a6 --- /dev/null +++ b/src/ftxui/dom/table.cpp @@ -0,0 +1,295 @@ +#include "ftxui/dom/table.hpp" + +#include // for max +#include // for allocator, shared_ptr, allocator_traits<>::value_type +#include // for move, swap + +#include "ftxui/dom/elements.hpp" // for Element, operator|, text, separatorCharacter, Elements, BorderStyle, Decorator, emptyElement, size, gridbox, EQUAL, flex, flex_shrink, HEIGHT, WIDTH + +namespace ftxui { +namespace { + +bool IsCell(int x, int y) { + return x % 2 == 1 && y % 2 == 1; +} + +static std::string charset[6][6] = { + {"┌", "┐", "└", "┘", "─", "│"}, // + {"┏", "┓", "┗", "┛", "━", "┃"}, // + {"╔", "╗", "╚", "╝", "═", "║"}, // + {"╭", "╮", "╰", "╯", "─", "│"}, // + {" ", " ", " ", " ", " ", " "}, // +}; + +int Wrap(int input, int modulo) { + input %= modulo; + input += modulo; + input %= modulo; + return input; +} + +void Order(int& a, int& b) { + if (a >= b) + std::swap(a, b); +} + +} // namespace + +Table::Table(std::vector> input) { + input_dim_y_ = input.size(); + input_dim_x_ = 0; + for (auto& row : input) + input_dim_x_ = std::max(input_dim_x_, (int)row.size()); + + dim_y_ = 2 * input_dim_y_ + 1; + dim_x_ = 2 * input_dim_x_ + 1; + + // Reserve space. + elements_.resize(dim_y_); + for (int y = 0; y < dim_y_; ++y) + elements_[y].resize(dim_x_); + + // Transfert elements_ from |input| toward |elements_|. + { + int y = 1; + for (auto& row : input) { + int x = 1; + for (auto& cell : row) { + elements_[y][x] = text(cell); + x += 2; + } + y += 2; + } + } + + // Add empty element for the border. + for (int y = 0; y < dim_y_; ++y) { + for (int x = 0; x < dim_x_; ++x) { + auto& element = elements_[y][x]; + + if (IsCell(x, y)) { + if (!element) + element = emptyElement(); + continue; + } + + element = emptyElement(); + } + } +} + +TableSelection Table::SelectRow(int index) { + return SelectRectangle(0, -1, index, index); +} + +TableSelection Table::SelectRows(int row_min, int row_max) { + return SelectRectangle(0, -1, row_min, row_max); +} + +TableSelection Table::SelectColumn(int index) { + return SelectRectangle(index, index, 0, -1); +} + +TableSelection Table::SelectColumns(int column_min, int column_max) { + return SelectRectangle(column_min, column_max, 0, -1); +} + +TableSelection Table::SelectCell(int column, int row) { + return SelectRectangle(column, column, row, row); +} + +TableSelection Table::SelectRectangle(int column_min, + int column_max, + int row_min, + int row_max) { + column_min = Wrap(column_min, input_dim_x_); + column_max = Wrap(column_max, input_dim_x_); + Order(column_min, column_max); + row_min = Wrap(row_min, input_dim_y_); + row_max = Wrap(row_max, input_dim_y_); + Order(row_min, row_max); + + TableSelection output; + output.table_ = this; + output.x_min_ = 2 * column_min; + output.x_max_ = 2 * column_max + 2; + output.y_min_ = 2 * row_min; + output.y_max_ = 2 * row_max + 2; + return output; +} + +TableSelection Table::SelectAll() { + TableSelection output; + output.table_ = this; + output.x_min_ = 0; + output.x_max_ = dim_x_ - 1; + output.y_min_ = 0; + output.y_max_ = dim_y_ - 1; + return output; +} + +Element Table::Render() { + for (int y = 0; y < dim_y_; ++y) { + for (int x = 0; x < dim_x_; ++x) { + auto& it = elements_[y][x]; + + // Line + if ((x + y) % 2 == 1) { + it = std::move(it) | flex; + continue; + } + + // Cells + if ((x % 2) == 1 && (y % 2) == 1) { + it = std::move(it) | flex_shrink; + continue; + } + + // Corners + it = std::move(it) | size(WIDTH, EQUAL, 0) | size(HEIGHT, EQUAL, 0); + } + } + return gridbox(std::move(elements_)); +} + +void TableSelection::Decorate(Decorator decorator) { + for (int y = y_min_; y <= y_max_; ++y) { + for (int x = x_min_; x <= x_max_; ++x) { + Element& e = table_->elements_[y][x]; + e = std::move(e) | decorator; + } + } +} + +void TableSelection::DecorateCells(Decorator decorator) { + for (int y = y_min_; y <= y_max_; ++y) { + for (int x = x_min_; x <= x_max_; ++x) { + if (y % 2 && x % 2) { + Element& e = table_->elements_[y][x]; + e = std::move(e) | decorator; + } + } + } +} + +void TableSelection::DecorateAlternateColumn(Decorator decorator, + int modulo, + int shift) { + for (int y = y_min_; y <= y_max_; ++y) { + for (int x = x_min_; x <= x_max_; ++x) { + if (y % 2 && (x / 2) % modulo == shift) { + Element& e = table_->elements_[y][x]; + e = std::move(e) | decorator; + } + } + } +} + +void TableSelection::DecorateAlternateRow(Decorator decorator, + int modulo, + int shift) { + for (int y = y_min_ + 1; y <= y_max_ - 1; ++y) { + for (int x = x_min_; x <= x_max_; ++x) { + if (y % 2 && (y / 2) % modulo == shift) { + Element& e = table_->elements_[y][x]; + e = std::move(e) | decorator; + } + } + } +} + +void TableSelection::DecorateCellsAlternateColumn(Decorator decorator, + int modulo, + int shift) { + for (int y = y_min_; y <= y_max_; ++y) { + for (int x = x_min_; x <= x_max_; ++x) { + if (y % 2 && x % 2 && ((x / 2) % modulo == shift)) { + Element& e = table_->elements_[y][x]; + e = std::move(e) | decorator; + } + } + } +} + +void TableSelection::DecorateCellsAlternateRow(Decorator decorator, + int modulo, + int shift) { + for (int y = y_min_; y <= y_max_; ++y) { + for (int x = x_min_; x <= x_max_; ++x) { + if (y % 2 && x % 2 && ((y / 2) % modulo == shift)) { + Element& e = table_->elements_[y][x]; + e = std::move(e) | decorator; + } + } + } +} + +void TableSelection::Border(BorderStyle style) { + BorderLeft(style); + BorderRight(style); + BorderTop(style); + BorderBottom(style); + + table_->elements_[y_min_][x_min_] = text(charset[style][0]); + table_->elements_[y_min_][x_max_] = text(charset[style][1]); + table_->elements_[y_max_][x_min_] = text(charset[style][2]); + table_->elements_[y_max_][x_max_] = text(charset[style][3]); +} + +void TableSelection::Separator(BorderStyle style) { + for (int y = y_min_ + 1; y <= y_max_ - 1; ++y) { + for (int x = x_min_ + 1; x <= x_max_ - 1; ++x) { + if (y % 2 == 0 || x % 2 == 0) { + Element& e = table_->elements_[y][x]; + e = (y % 2) ? separatorCharacter(charset[style][5]) + : separatorCharacter(charset[style][4]); + } + } + } +} + +void TableSelection::SeparatorVertical(BorderStyle style) { + for (int y = y_min_ + 1; y <= y_max_ - 1; ++y) { + for (int x = x_min_ + 1; x <= x_max_ - 1; ++x) { + if (x % 2 == 0) { + table_->elements_[y][x] = text(charset[style][5]); + } + } + } +} + +void TableSelection::SeparatorHorizontal(BorderStyle style) { + for (int y = y_min_ + 1; y <= y_max_ - 1; ++y) { + for (int x = x_min_ + 1; x <= x_max_ - 1; ++x) { + if (y % 2 == 0) { + table_->elements_[y][x] = text(charset[style][4]); + } + } + } +} + +void TableSelection::BorderLeft(BorderStyle style) { + for (int y = y_min_; y <= y_max_; y++) + table_->elements_[y][x_min_] = separatorCharacter(charset[style][5]); +} + +void TableSelection::BorderRight(BorderStyle style) { + for (int y = y_min_; y <= y_max_; y++) + table_->elements_[y][x_max_] = separatorCharacter(charset[style][5]); +} + +void TableSelection::BorderTop(BorderStyle style) { + for (int x = x_min_; x <= x_max_; x++) + table_->elements_[y_min_][x] = separatorCharacter(charset[style][4]); +} + +void TableSelection::BorderBottom(BorderStyle style) { + for (int x = x_min_; x <= x_max_; x++) + table_->elements_[y_max_][x] = separatorCharacter(charset[style][4]); +} + +} // namespace ftxui + +// Copyright 2021 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/src/ftxui/dom/table_test.cpp b/src/ftxui/dom/table_test.cpp new file mode 100644 index 000000000..41777cf42 --- /dev/null +++ b/src/ftxui/dom/table_test.cpp @@ -0,0 +1,716 @@ +#include // for Message +#include // for SuiteApiResolver, TestFactoryImpl, TestPartResult +#include // for allocator + +#include "ftxui/dom/elements.hpp" // for LIGHT, flex, center, EMPTY, DOUBLE +#include "ftxui/dom/node.hpp" // for Render +#include "ftxui/dom/table.hpp" +#include "ftxui/screen/box.hpp" // for ftxui +#include "ftxui/screen/screen.hpp" // for Screen +#include "gtest/gtest_pred_impl.h" // for Test, EXPECT_EQ, TEST + +using namespace ftxui; + +TEST(TableTest, Empty) { + auto table = Table({}); + Screen screen(5, 5); + Render(screen, table.Render()); + EXPECT_EQ( + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, Basic) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + "abcd \r\n" + "efgh \r\n" + "ijkl \r\n" + "mnop \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, SeparatorVerticalEmpty) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectAll().SeparatorVertical(EMPTY); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + "a b c d \r\n" + "e f g h \r\n" + "i j k l \r\n" + "m n o p \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, SeparatorHorizontalEmpty) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectAll().SeparatorHorizontal(EMPTY); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + "abcd \r\n" + " \r\n" + "efgh \r\n" + " \r\n" + "ijkl \r\n" + " \r\n" + "mnop \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, SeparatorHorizontalLight) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectAll().SeparatorHorizontal(LIGHT); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + "abcd \r\n" + "──── \r\n" + "efgh \r\n" + "──── \r\n" + "ijkl \r\n" + "──── \r\n" + "mnop \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, SeparatorVerticalLight) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectAll().SeparatorVertical(LIGHT); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + "a│b│c│d \r\n" + "e│f│g│h \r\n" + "i│j│k│l \r\n" + "m│n│o│p \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, SeparatorLight) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectAll().Separator(LIGHT); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + "a│b│c│d \r\n" + "─┼─┼─┼─ \r\n" + "e│f│g│h \r\n" + "─┼─┼─┼─ \r\n" + "i│j│k│l \r\n" + "─┼─┼─┼─ \r\n" + "m│n│o│p \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, SeparatorVerticalHorizontalLight) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectAll().SeparatorVertical(LIGHT); + table.SelectAll().SeparatorHorizontal(LIGHT); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + "a│b│c│d \r\n" + "─┼─┼─┼─ \r\n" + "e│f│g│h \r\n" + "─┼─┼─┼─ \r\n" + "i│j│k│l \r\n" + "─┼─┼─┼─ \r\n" + "m│n│o│p \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, SeparatorHorizontalVerticalLight) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectAll().SeparatorHorizontal(LIGHT); + table.SelectAll().SeparatorVertical(LIGHT); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + "a│b│c│d \r\n" + "─┼─┼─┼─ \r\n" + "e│f│g│h \r\n" + "─┼─┼─┼─ \r\n" + "i│j│k│l \r\n" + "─┼─┼─┼─ \r\n" + "m│n│o│p \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, BorderLight) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectAll().Border(LIGHT); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + "┌────┐ \r\n" + "│abcd│ \r\n" + "│efgh│ \r\n" + "│ijkl│ \r\n" + "│mnop│ \r\n" + "└────┘ \r\n" + " \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, BorderSeparatorLight) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectAll().Border(LIGHT); + table.SelectAll().Separator(LIGHT); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + "┌─┬─┬─┬─┐ \r\n" + "│a│b│c│d│ \r\n" + "├─┼─┼─┼─┤ \r\n" + "│e│f│g│h│ \r\n" + "├─┼─┼─┼─┤ \r\n" + "│i│j│k│l│ \r\n" + "├─┼─┼─┼─┤ \r\n" + "│m│n│o│p│ \r\n" + "└─┴─┴─┴─┘ \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, SelectRow) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectRow(1).Border(LIGHT); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + " abcd \r\n" + "┌────┐ \r\n" + "│efgh│ \r\n" + "└────┘ \r\n" + " ijkl \r\n" + " mnop \r\n" + " \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, SelectRowNegative) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectRow(-2).Border(LIGHT); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + " abcd \r\n" + " efgh \r\n" + "┌────┐ \r\n" + "│ijkl│ \r\n" + "└────┘ \r\n" + " mnop \r\n" + " \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, SelectColumn) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectColumn(1).Border(LIGHT); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + " ┌─┐ \r\n" + "a│b│cd \r\n" + "e│f│gh \r\n" + "i│j│kl \r\n" + "m│n│op \r\n" + " └─┘ \r\n" + " \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, SelectColumnNegative) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectColumn(-2).Border(LIGHT); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + " ┌─┐ \r\n" + "ab│c│d \r\n" + "ef│g│h \r\n" + "ij│k│l \r\n" + "mn│o│p \r\n" + " └─┘ \r\n" + " \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, CrossingBorders) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectRow(1).Border(LIGHT); + table.SelectColumn(1).Border(LIGHT); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + " ┌─┐ \r\n" + " a│b│cd \r\n" + "┌─┼─┼──┐ \r\n" + "│e│f│gh│ \r\n" + "└─┼─┼──┘ \r\n" + " i│j│kl \r\n" + " m│n│op \r\n" + " └─┘ \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, CrossingBordersLightAndDouble) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectRow(1).Border(LIGHT); + table.SelectColumn(1).Border(DOUBLE); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + " ╔═╗ \r\n" + " a║b║cd \r\n" + "┌─╫─╫──┐ \r\n" + "│e║f║gh│ \r\n" + "└─╫─╫──┘ \r\n" + " i║j║kl \r\n" + " m║n║op \r\n" + " ╚═╝ \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, SelectColumns) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectColumns(1, 2).Border(LIGHT); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + " ┌──┐ \r\n" + "a│bc│d \r\n" + "e│fg│h \r\n" + "i│jk│l \r\n" + "m│no│p \r\n" + " └──┘ \r\n" + " \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, SelectRows) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectRows(1, 2).Border(LIGHT); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + " abcd \r\n" + "┌────┐ \r\n" + "│efgh│ \r\n" + "│ijkl│ \r\n" + "└────┘ \r\n" + " mnop \r\n" + " \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, SelectRectangle) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectRectangle(1, 2, 1, 2).Border(LIGHT); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + "a bc d \r\n" + " ┌──┐ \r\n" + "e│fg│h \r\n" + "i│jk│l \r\n" + " └──┘ \r\n" + "m no p \r\n" + " \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, SelectColumnsNegative) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectColumns(1, -1).Border(LIGHT); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + " ┌───┐ \r\n" + "a│bcd│ \r\n" + "e│fgh│ \r\n" + "i│jkl│ \r\n" + "m│nop│ \r\n" + " └───┘ \r\n" + " \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, SelectInverted) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectColumns(-1, 1).Border(LIGHT); + Screen screen(10, 10); + Render(screen, table.Render()); + EXPECT_EQ( + " ┌───┐ \r\n" + "a│bcd│ \r\n" + "e│fgh│ \r\n" + "i│jkl│ \r\n" + "m│nop│ \r\n" + " └───┘ \r\n" + " \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, ColumnFlex) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectAll().Border(LIGHT); + table.SelectAll().Separator(LIGHT); + table.SelectColumn(1).Decorate(flex); + Screen screen(20, 10); + Render(screen, table.Render()); + EXPECT_EQ( + "┌─┬────────────┬─┬─┐\r\n" + "│a│b │c│d│\r\n" + "├─┼────────────┼─┼─┤\r\n" + "│e│f │g│h│\r\n" + "├─┼────────────┼─┼─┤\r\n" + "│i│j │k│l│\r\n" + "├─┼────────────┼─┼─┤\r\n" + "│m│n │o│p│\r\n" + "└─┴────────────┴─┴─┘\r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, ColumnFlexCenter) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectAll().Border(LIGHT); + table.SelectAll().Separator(LIGHT); + table.SelectColumn(1).Decorate(flex); + table.SelectColumn(1).DecorateCells(center); + Screen screen(20, 10); + Render(screen, table.Render()); + EXPECT_EQ( + "┌─┬─┬─┬─┐ \r\n" + "│a│b│c│d│ \r\n" + "├─┼─┼─┼─┤ \r\n" + "│e│f│g│h│ \r\n" + "├─┼─┼─┼─┤ \r\n" + "│i│j│k│l│ \r\n" + "├─┼─┼─┼─┤ \r\n" + "│m│n│o│p│ \r\n" + "└─┴─┴─┴─┘ \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, ColumnCenter) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectAll().Border(LIGHT); + table.SelectAll().Separator(LIGHT); + table.SelectColumn(1).DecorateCells(center); + Screen screen(20, 10); + Render(screen, table.Render()); + EXPECT_EQ( + "┌─┬─┬─┬─┐ \r\n" + "│a│b│c│d│ \r\n" + "├─┼─┼─┼─┤ \r\n" + "│e│f│g│h│ \r\n" + "├─┼─┼─┼─┤ \r\n" + "│i│j│k│l│ \r\n" + "├─┼─┼─┼─┤ \r\n" + "│m│n│o│p│ \r\n" + "└─┴─┴─┴─┘ \r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, ColumnFlexTwo) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectAll().Border(LIGHT); + table.SelectAll().Separator(LIGHT); + table.SelectColumn(1).Decorate(flex); + table.SelectColumn(3).Decorate(flex); + Screen screen(20, 10); + Render(screen, table.Render()); + EXPECT_EQ( + "┌─┬──────┬─┬───────┐\r\n" + "│a│b │c│d │\r\n" + "├─┼──────┼─┼───────┤\r\n" + "│e│f │g│h │\r\n" + "├─┼──────┼─┼───────┤\r\n" + "│i│j │k│l │\r\n" + "├─┼──────┼─┼───────┤\r\n" + "│m│n │o│p │\r\n" + "└─┴──────┴─┴───────┘\r\n" + " ", + screen.ToString()); +} + +TEST(TableTest, RowFlex) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectAll().Border(LIGHT); + table.SelectAll().Separator(LIGHT); + table.SelectRow(1).Decorate(flex); + Screen screen(10, 20); + Render(screen, table.Render()); + EXPECT_EQ( + "┌─┬─┬─┬─┐ \r\n" + "│a│b│c│d│ \r\n" + "├─┼─┼─┼─┤ \r\n" + "│e│f│g│h│ \r\n" + "│ │ │ │ │ \r\n" + "│ │ │ │ │ \r\n" + "│ │ │ │ │ \r\n" + "│ │ │ │ │ \r\n" + "│ │ │ │ │ \r\n" + "│ │ │ │ │ \r\n" + "│ │ │ │ │ \r\n" + "│ │ │ │ │ \r\n" + "│ │ │ │ │ \r\n" + "│ │ │ │ │ \r\n" + "│ │ │ │ │ \r\n" + "├─┼─┼─┼─┤ \r\n" + "│i│j│k│l│ \r\n" + "├─┼─┼─┼─┤ \r\n" + "│m│n│o│p│ \r\n" + "└─┴─┴─┴─┘ ", + screen.ToString()); +} + +TEST(TableTest, RowFlexTwo) { + auto table = Table({ + {"a", "b", "c", "d"}, + {"e", "f", "g", "h"}, + {"i", "j", "k", "l"}, + {"m", "n", "o", "p"}, + }); + table.SelectAll().Border(LIGHT); + table.SelectAll().Separator(LIGHT); + table.SelectRow(1).Decorate(flex); + table.SelectRow(3).Decorate(flex); + Screen screen(10, 20); + Render(screen, table.Render()); + EXPECT_EQ( + "┌─┬─┬─┬─┐ \r\n" + "│a│b│c│d│ \r\n" + "├─┼─┼─┼─┤ \r\n" + "│e│f│g│h│ \r\n" + "│ │ │ │ │ \r\n" + "│ │ │ │ │ \r\n" + "│ │ │ │ │ \r\n" + "│ │ │ │ │ \r\n" + "│ │ │ │ │ \r\n" + "├─┼─┼─┼─┤ \r\n" + "│i│j│k│l│ \r\n" + "├─┼─┼─┼─┤ \r\n" + "│m│n│o│p│ \r\n" + "│ │ │ │ │ \r\n" + "│ │ │ │ │ \r\n" + "│ │ │ │ │ \r\n" + "│ │ │ │ │ \r\n" + "│ │ │ │ │ \r\n" + "│ │ │ │ │ \r\n" + "└─┴─┴─┴─┘ ", + screen.ToString()); +} + +// Copyright 2021 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/src/ftxui/dom/util.cpp b/src/ftxui/dom/util.cpp index 689f07937..111a51abf 100644 --- a/src/ftxui/dom/util.cpp +++ b/src/ftxui/dom/util.cpp @@ -75,6 +75,18 @@ Dimensions Dimension::Fit(Element& e) { std::min(e->requirement().min_y, size.dimy)}; } +/// An element of size 0x0 drawing nothing. +/// @ingroup dom +Element emptyElement() { + class Impl : public Node { + void ComputeRequirement() override { + requirement_.min_x = 0; + requirement_.min_x = 0; + } + }; + return std::make_unique(); +} + } // namespace ftxui // Copyright 2020 Arthur Sonzogni. All rights reserved. diff --git a/src/ftxui/screen/screen.cpp b/src/ftxui/screen/screen.cpp index 9e9458957..ae2d9d086 100644 --- a/src/ftxui/screen/screen.cpp +++ b/src/ftxui/screen/screen.cpp @@ -1,4 +1,3 @@ -#include // for uint16_t #include // for operator<<, stringstream, basic_ostream, flush, cout, ostream #include // for _Rb_tree_const_iterator, map, operator!=, operator== #include // for allocator, allocator_traits<>::value_type