Gauge direction (#326)

Add `gauge` with all the different directions.

Co-authored-by: Aleksandar Brakmic <13668697+brakmic-aleksandar@users.noreply.github.com>
This commit is contained in:
Arthur Sonzogni 2022-02-06 19:17:21 +01:00 committed by GitHub
parent 7c3ca1beb5
commit 8ba3698437
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 369 additions and 19 deletions

View File

@ -4,6 +4,20 @@ Changelog
current (development) 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 2.0.0
----- -----

View File

@ -9,6 +9,7 @@ example(color_truecolor_RGB)
example(dbox) example(dbox)
example(canvas) example(canvas)
example(gauge) example(gauge)
example(gauge_direction)
example(graph) example(graph)
example(gridbox) example(gridbox)
example(hflow) example(hflow)

View File

@ -0,0 +1,79 @@
#include <chrono> // for operator""s, chrono_literals
#include <ftxui/dom/elements.hpp> // for text, gauge, operator|, flex, hbox, Element
#include <ftxui/screen/screen.hpp> // for Screen
#include <iostream> // for cout, endl, ostream
#include <string> // for allocator, operator+, char_traits, operator<<, string, to_string, basic_string
#include <thread> // 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.

View File

@ -21,6 +21,7 @@ using Decorator = std::function<Element(Element)>;
using GraphFunction = std::function<std::vector<int>(int, int)>; using GraphFunction = std::function<std::vector<int>(int, int)>;
enum BorderStyle { LIGHT, HEAVY, DOUBLE, ROUNDED, EMPTY }; enum BorderStyle { LIGHT, HEAVY, DOUBLE, ROUNDED, EMPTY };
enum class GaugeDirection { Left, Up, Right, Down };
// Pipe elements into decorator togethers. // Pipe elements into decorator togethers.
// For instance the next lines are equivalents: // For instance the next lines are equivalents:
@ -42,6 +43,11 @@ Element separatorStyled(BorderStyle);
Element separator(Pixel); Element separator(Pixel);
Element separatorCharacter(std::string); Element separatorCharacter(std::string);
Element gauge(float ratio); 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 border(Element);
Element borderLight(Element); Element borderLight(Element);
Element borderHeavy(Element); Element borderHeavy(Element);

View File

@ -10,7 +10,7 @@
namespace ftxui { namespace ftxui {
static std::string charset[11] = { static std::string charset_horizontal[11] = {
#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK) #if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
// Microsoft's terminals often use fonts not handling the 8 unicode // Microsoft's terminals often use fonts not handling the 8 unicode
// characters for representing the whole gauge. Fallback with less. // characters for representing the whole gauge. Fallback with less.
@ -22,38 +22,229 @@ static std::string charset[11] = {
// int(9 * (limit - limit_int) = 9 // 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 { class Gauge : public Node {
public: 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 { void ComputeRequirement() override {
switch (direction_) {
case GaugeDirection::Right:
case GaugeDirection::Left:
requirement_.flex_grow_x = 1; requirement_.flex_grow_x = 1;
requirement_.flex_grow_y = 0; requirement_.flex_grow_y = 0;
requirement_.flex_shrink_x = 1; requirement_.flex_shrink_x = 1;
requirement_.flex_shrink_y = 0; 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_x = 1;
requirement_.min_y = 1; requirement_.min_y = 1;
} }
void Render(Screen& screen) override { 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; int y = box_.y_min;
if (y > box_.y_max) if (y > box_.y_max)
return; return;
float limit = box_.x_min + progress_ * (box_.x_max - box_.x_min + 1); // 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 limit_int = limit;
int x = box_.x_min; int x = box_.x_min;
while (x < limit_int) while (x < limit_int)
screen.at(x++, y) = charset[9]; screen.at(x++, y) = charset_horizontal[9];
screen.at(x++, y) = charset[int(9 * (limit - limit_int))]; screen.at(x++, y) = charset_horizontal[int(9 * (limit - limit_int))];
while (x <= box_.x_max) while (x <= box_.x_max)
screen.at(x++, y) = charset[0]; 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;
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: private:
float progress_; 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<Gauge>(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. /// @brief Draw a high definition progress bar.
/// @param progress The proportion of the area to be filled. Belong to [0,1]. /// @param progress The proportion of the area to be filled. Belong to [0,1].
/// @ingroup dom /// @ingroup dom
@ -73,7 +264,7 @@ class Gauge : public Node {
/// └──────────────────────────────────────────────────────────────────────────┘ /// └──────────────────────────────────────────────────────────────────────────┘
/// ~~~ /// ~~~
Element gauge(float progress) { Element gauge(float progress) {
return std::make_shared<Gauge>(progress); return gaugeRight(progress);
} }
} // namespace ftxui } // namespace ftxui

View File

@ -11,7 +11,7 @@
using namespace ftxui; using namespace ftxui;
using namespace ftxui; using namespace ftxui;
TEST(GaugeTest, zero) { TEST(GaugeTest, ZeroHorizontal) {
auto root = gauge(0); auto root = gauge(0);
Screen screen(11, 1); Screen screen(11, 1);
Render(screen, root); Render(screen, root);
@ -19,16 +19,15 @@ TEST(GaugeTest, zero) {
EXPECT_EQ(" ", screen.ToString()); EXPECT_EQ(" ", screen.ToString());
} }
TEST(GaugeTest, half) { TEST(GaugeTest, HalfHorizontal) {
auto root = gauge(0.5); auto root = gauge(0.5);
Screen screen(11, 1); Screen screen(11, 1);
Render(screen, root); Render(screen, root);
EXPECT_EQ("█████▍ ", screen.ToString()); EXPECT_EQ("█████▍ ", screen.ToString());
//" ▏▎▍▌▊▉█";
} }
TEST(GaugeTest, one) { TEST(GaugeTest, OneHorizontal) {
auto root = gauge(1.0); auto root = gauge(1.0);
Screen screen(11, 1); Screen screen(11, 1);
Render(screen, root); Render(screen, root);
@ -36,6 +35,66 @@ TEST(GaugeTest, one) {
EXPECT_EQ("███████████", screen.ToString()); 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. // Copyright 2020 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in // Use of this source code is governed by the MIT license that can be found in
// the LICENSE file. // the LICENSE file.

View File

@ -17,7 +17,7 @@ class Inverted : public NodeDecorator {
Node::Render(screen); Node::Render(screen);
for (int y = box_.y_min; y <= box_.y_max; ++y) { for (int y = box_.y_min; y <= box_.y_max; ++y) {
for (int x = box_.x_min; x <= box_.x_max; ++x) { for (int x = box_.x_min; x <= box_.x_max; ++x) {
screen.PixelAt(x, y).inverted = true; screen.PixelAt(x, y).inverted ^= true;
} }
} }
} }