mirror of
				https://github.com/ArthurSonzogni/FTXUI.git
				synced 2025-10-31 02:28:11 +08:00 
			
		
		
		
	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:
		
							
								
								
									
										14
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								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 | ||||
| ----- | ||||
|  | ||||
|   | ||||
| @@ -9,6 +9,7 @@ example(color_truecolor_RGB) | ||||
| example(dbox) | ||||
| example(canvas) | ||||
| example(gauge) | ||||
| example(gauge_direction) | ||||
| example(graph) | ||||
| example(gridbox) | ||||
| example(hflow) | ||||
|   | ||||
							
								
								
									
										79
									
								
								examples/dom/gauge_direction.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								examples/dom/gauge_direction.cpp
									
									
									
									
									
										Normal 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. | ||||
| @@ -21,6 +21,7 @@ using Decorator = std::function<Element(Element)>; | ||||
| using GraphFunction = std::function<std::vector<int>(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); | ||||
|   | ||||
| @@ -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 { | ||||
|     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); | ||||
|     // 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[9]; | ||||
|     screen.at(x++, y) = charset[int(9 * (limit - 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[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: | ||||
|   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. | ||||
| /// @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<Gauge>(progress); | ||||
|   return gaugeRight(progress); | ||||
| } | ||||
|  | ||||
| }  // namespace ftxui | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Arthur Sonzogni
					Arthur Sonzogni