From 8ba3698437ec235875acea1761f74529a2f129aa Mon Sep 17 00:00:00 2001 From: Arthur Sonzogni Date: Sun, 6 Feb 2022 19:17:21 +0100 Subject: [PATCH] Gauge direction (#326) Add `gauge` with all the different directions. Co-authored-by: Aleksandar Brakmic <13668697+brakmic-aleksandar@users.noreply.github.com> --- CHANGELOG.md | 14 ++ examples/dom/CMakeLists.txt | 1 + examples/dom/gauge_direction.cpp | 79 +++++++++++ include/ftxui/dom/elements.hpp | 6 + src/ftxui/dom/gauge.cpp | 219 +++++++++++++++++++++++++++++-- src/ftxui/dom/gauge_test.cpp | 67 +++++++++- src/ftxui/dom/inverted.cpp | 2 +- 7 files changed, 369 insertions(+), 19 deletions(-) create mode 100644 examples/dom/gauge_direction.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 2070a39..cddd9ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ Changelog current (development) --------------------- +### Features: + +#### DOM: +- The `inverted` decorator now toggle in the inverted attribute. +- Add `gauge` for the 4 directions. Expose the following API: +```cpp +Element gauge(float ratio); +Element gaugeLeft(float ratio); +Element gaugeRight(float ratio); +Element gaugeUp(float ratio); +Element gaugeDown(float ratio); +Element gaugeDirection(float ratio, GaugeDirection); +``` + 2.0.0 ----- diff --git a/examples/dom/CMakeLists.txt b/examples/dom/CMakeLists.txt index f7c720c..ee2265c 100644 --- a/examples/dom/CMakeLists.txt +++ b/examples/dom/CMakeLists.txt @@ -9,6 +9,7 @@ example(color_truecolor_RGB) example(dbox) example(canvas) example(gauge) +example(gauge_direction) example(graph) example(gridbox) example(hflow) diff --git a/examples/dom/gauge_direction.cpp b/examples/dom/gauge_direction.cpp new file mode 100644 index 0000000..5998447 --- /dev/null +++ b/examples/dom/gauge_direction.cpp @@ -0,0 +1,79 @@ +#include // for operator""s, chrono_literals +#include // for text, gauge, operator|, flex, hbox, Element +#include // for Screen +#include // for cout, endl, ostream +#include // for allocator, operator+, char_traits, operator<<, string, to_string, basic_string +#include // for sleep_for + +#include "ftxui/dom/node.hpp" // for Render +#include "ftxui/screen/color.hpp" // for ftxui + +int main(int argc, const char* argv[]) { + using namespace ftxui; + using namespace std::chrono_literals; + + std::string reset_position; + for (float percentage = 0.0f; percentage <= 1.0f; percentage += 0.002f) { + std::string data_downloaded = + std::to_string(int(percentage * 5000)) + "/5000"; + + auto gauge_up = // + hbox({ + vtext("gauge vertical"), + separator(), + gaugeUp(percentage), + }) | + border; + + auto gauge_down = // + hbox({ + vtext("gauge vertical"), + separator(), + gaugeDown(percentage), + }) | + border; + + auto gauge_right = // + vbox({ + text("gauge horizontal"), + separator(), + gaugeRight(percentage), + }) | + border; + + auto gauge_left = // + vbox({ + text("gauge horizontal"), + separator(), + gaugeLeft(percentage), + }) | + border; + + auto document = hbox({ + gauge_up, + filler(), + vbox({ + gauge_right, + filler(), + text(data_downloaded) | border | center, + filler(), + gauge_left, + }), + filler(), + gauge_down, + }); + + auto screen = Screen(32, 16); + Render(screen, document); + std::cout << reset_position; + screen.Print(); + reset_position = screen.ResetPosition(); + + std::this_thread::sleep_for(0.01s); + } + std::cout << std::endl; +} + +// Copyright 2022 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 dea66a6..95c09ff 100644 --- a/include/ftxui/dom/elements.hpp +++ b/include/ftxui/dom/elements.hpp @@ -21,6 +21,7 @@ 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: @@ -42,6 +43,11 @@ Element separatorStyled(BorderStyle); Element separator(Pixel); Element separatorCharacter(std::string); Element gauge(float ratio); +Element gaugeLeft(float ratio); +Element gaugeRight(float ratio); +Element gaugeUp(float ratio); +Element gaugeDown(float ratio); +Element gaugeDirection(float ratio, GaugeDirection); Element border(Element); Element borderLight(Element); Element borderHeavy(Element); diff --git a/src/ftxui/dom/gauge.cpp b/src/ftxui/dom/gauge.cpp index 68de4cd..b2fb6f0 100644 --- a/src/ftxui/dom/gauge.cpp +++ b/src/ftxui/dom/gauge.cpp @@ -10,7 +10,7 @@ namespace ftxui { -static std::string charset[11] = { +static std::string charset_horizontal[11] = { #if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK) // Microsoft's terminals often use fonts not handling the 8 unicode // characters for representing the whole gauge. Fallback with less. @@ -22,38 +22,229 @@ static std::string charset[11] = { // int(9 * (limit - limit_int) = 9 "█"}; +static std::string charset_vertical[10] = { + "█", + "▇", + "▆", + "▅", + "▄", + "▃", + "▂", + "▁", + " ", + // An extra character in case when the fuzzer manage to have: + // int(8 * (limit - limit_int) = 8 + " ", +}; + class Gauge : public Node { public: - Gauge(float progress) : progress_(std::min(std::max(progress, 0.f), 1.f)) {} + Gauge(float progress, GaugeDirection direction) + : progress_(std::min(std::max(progress, 0.f), 1.f)), + direction_(direction) {} void ComputeRequirement() override { - requirement_.flex_grow_x = 1; - requirement_.flex_grow_y = 0; - requirement_.flex_shrink_x = 1; - requirement_.flex_shrink_y = 0; + switch (direction_) { + case GaugeDirection::Right: + case GaugeDirection::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: + requirement_.flex_grow_x = 0; + requirement_.flex_grow_y = 1; + requirement_.flex_shrink_x = 0; + requirement_.flex_shrink_y = 1; + break; + } requirement_.min_x = 1; requirement_.min_y = 1; } void Render(Screen& screen) override { + switch (direction_) { + case GaugeDirection::Right: + RenderHorizontal(screen, /*invert=*/false); + break; + case GaugeDirection::Up: + RenderVertical(screen, /*invert=*/false); + break; + case GaugeDirection::Left: + RenderHorizontal(screen, /*invert=*/true); + break; + case GaugeDirection::Down: + RenderVertical(screen, /*invert=*/true); + break; + } + } + + void RenderHorizontal(Screen& screen, bool invert) { int y = box_.y_min; if (y > box_.y_max) return; - float limit = box_.x_min + progress_ * (box_.x_max - box_.x_min + 1); - int limit_int = limit; + // Draw the progress bar horizontally. + { + float progress = invert ? 1.f - progress_ : progress_; + float limit = box_.x_min + progress * (box_.x_max - box_.x_min + 1); + int limit_int = limit; + int x = box_.x_min; + while (x < limit_int) + screen.at(x++, y) = charset_horizontal[9]; + screen.at(x++, y) = charset_horizontal[int(9 * (limit - limit_int))]; + while (x <= box_.x_max) + screen.at(x++, y) = charset_horizontal[0]; + } + + if (invert) { + for (int x = box_.x_min; x <= box_.x_max; x++) + screen.PixelAt(x, y).inverted ^= true; + } + } + + void RenderVertical(Screen& screen, bool invert) { int x = box_.x_min; - while (x < limit_int) - screen.at(x++, y) = charset[9]; - screen.at(x++, y) = charset[int(9 * (limit - limit_int))]; - while (x <= box_.x_max) - screen.at(x++, y) = charset[0]; + if (x > box_.x_max) + return; + + // Draw the progress bar vertically: + { + float progress = invert ? progress_ : 1.f - progress_; + float limit = box_.y_min + progress * (box_.y_max - box_.y_min + 1); + int limit_int = limit; + int y = box_.y_min; + while (y < limit_int) + screen.at(x, y++) = charset_vertical[8]; + screen.at(x, y++) = charset_vertical[int(8 * (limit - limit_int))]; + while (y <= box_.y_max) + screen.at(x, y++) = charset_vertical[0]; + } + + if (invert) { + for (int y = box_.y_min; y <= box_.y_max; y++) + screen.PixelAt(x, y).inverted ^= true; + } } private: float progress_; + GaugeDirection direction_; }; +/// @brief Draw a high definition progress bar progressing in specified +/// direction. +/// @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) { + return std::make_shared(progress, direction); +} + +/// @brief Draw a high definition progress bar progressing from left to right. +/// @param progress The proportion of the area to be filled. Belong to [0,1]. +/// @ingroup dom +/// +/// ### Example +/// +/// A gauge. It can be used to represent a progress bar. +/// ~~~cpp +/// border(gaugeRight(0.5)) +/// ~~~ +/// +/// #### Output +/// +/// ~~~bash +/// ┌──────────────────────────────────────────────────────────────────────────┐ +/// │█████████████████████████████████████ │ +/// └──────────────────────────────────────────────────────────────────────────┘ +/// ~~~ +Element gaugeRight(float progress) { + return gaugeDirection(progress, GaugeDirection::Right); +} + +/// @brief Draw a high definition progress bar progressing from right to left. +/// @param progress The proportion of the area to be filled. Belong to [0,1]. +/// @ingroup dom +/// +/// ### Example +/// +/// A gauge. It can be used to represent a progress bar. +/// ~~~cpp +/// border(gaugeLeft(0.5)) +/// ~~~ +/// +/// #### Output +/// +/// ~~~bash +/// ┌──────────────────────────────────────────────────────────────────────────┐ +/// │ █████████████████████████████████████│ +/// └──────────────────────────────────────────────────────────────────────────┘ +/// ~~~ +Element gaugeLeft(float progress) { + return gaugeDirection(progress, GaugeDirection::Left); +} + +/// @brief Draw a high definition progress bar progressing from bottom to top. +/// @param progress The proportion of the area to be filled. Belong to [0,1]. +/// @ingroup dom +/// +/// ### Example +/// +/// A gauge. It can be used to represent a progress bar. +/// ~~~cpp +/// border(gaugeUp(0.5)) +/// ~~~ +/// +/// #### Output +/// +/// ~~~bash +/// ┌─┐ +/// │ │ +/// │ │ +/// │ │ +/// │ │ +/// │█│ +/// │█│ +/// │█│ +/// │█│ +/// └─┘ +/// ~~~ +Element gaugeUp(float progress) { + return gaugeDirection(progress, GaugeDirection::Up); +} + +/// @brief Draw a high definition progress bar progressing from top to bottom. +/// @param progress The proportion of the area to be filled. Belong to [0,1]. +/// @ingroup dom +/// +/// ### Example +/// +/// A gauge. It can be used to represent a progress bar. +/// ~~~cpp +/// border(gaugeDown(0.5)) +/// ~~~ +/// +/// #### Output +/// +/// ~~~bash +/// ┌─┐ +/// │█│ +/// │█│ +/// │█│ +/// │█│ +/// │ │ +/// │ │ +/// │ │ +/// │ │ +/// └─┘ +/// ~~~ +Element gaugeDown(float progress) { + return gaugeDirection(progress, GaugeDirection::Down); +} + /// @brief Draw a high definition progress bar. /// @param progress The proportion of the area to be filled. Belong to [0,1]. /// @ingroup dom @@ -73,7 +264,7 @@ class Gauge : public Node { /// └──────────────────────────────────────────────────────────────────────────┘ /// ~~~ Element gauge(float progress) { - return std::make_shared(progress); + return gaugeRight(progress); } } // namespace ftxui diff --git a/src/ftxui/dom/gauge_test.cpp b/src/ftxui/dom/gauge_test.cpp index ced0d54..8165426 100644 --- a/src/ftxui/dom/gauge_test.cpp +++ b/src/ftxui/dom/gauge_test.cpp @@ -11,7 +11,7 @@ using namespace ftxui; using namespace ftxui; -TEST(GaugeTest, zero) { +TEST(GaugeTest, ZeroHorizontal) { auto root = gauge(0); Screen screen(11, 1); Render(screen, root); @@ -19,16 +19,15 @@ TEST(GaugeTest, zero) { EXPECT_EQ(" ", screen.ToString()); } -TEST(GaugeTest, half) { +TEST(GaugeTest, HalfHorizontal) { auto root = gauge(0.5); Screen screen(11, 1); Render(screen, root); EXPECT_EQ("█████▍ ", screen.ToString()); - //" ▏▎▍▌▊▉█"; } -TEST(GaugeTest, one) { +TEST(GaugeTest, OneHorizontal) { auto root = gauge(1.0); Screen screen(11, 1); Render(screen, root); @@ -36,6 +35,66 @@ TEST(GaugeTest, one) { EXPECT_EQ("███████████", screen.ToString()); } +TEST(GaugeTest, ZeroVertical) { + auto root = gaugeUp(0); + Screen screen(1, 11); + Render(screen, root); + + EXPECT_EQ( + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " ", + screen.ToString()); +} + +TEST(GaugeTest, HalfVertical) { + auto root = gaugeUp(0.5); + Screen screen(1, 11); + Render(screen, root); + + EXPECT_EQ( + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + "▄\r\n" + "█\r\n" + "█\r\n" + "█\r\n" + "█\r\n" + "█", + screen.ToString()); +} + +TEST(GaugeTest, OneVertical) { + auto root = gaugeUp(1.0); + Screen screen(1, 11); + Render(screen, root); + + EXPECT_EQ( + "█\r\n" + "█\r\n" + "█\r\n" + "█\r\n" + "█\r\n" + "█\r\n" + "█\r\n" + "█\r\n" + "█\r\n" + "█\r\n" + "█", + screen.ToString()); +} + // 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/src/ftxui/dom/inverted.cpp b/src/ftxui/dom/inverted.cpp index bad2c1d..4e8c6c8 100644 --- a/src/ftxui/dom/inverted.cpp +++ b/src/ftxui/dom/inverted.cpp @@ -17,7 +17,7 @@ class Inverted : public NodeDecorator { Node::Render(screen); 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).inverted = true; + screen.PixelAt(x, y).inverted ^= true; } } }