From 293ff179f64727c28535a98451ef80d859172d16 Mon Sep 17 00:00:00 2001 From: Dimo Markov Date: Sat, 27 Apr 2024 12:03:44 +0300 Subject: [PATCH] Separate a reusable Image class from Screen (#834) Co-authored-by: ArthurSonzogni --- .gitignore | 4 ++ CHANGELOG.md | 3 ++ CMakeLists.txt | 3 ++ include/ftxui/dom/canvas.hpp | 19 ++++++--- include/ftxui/screen/box.hpp | 1 + include/ftxui/screen/image.hpp | 50 ++++++++++++++++++++++++ include/ftxui/screen/pixel.hpp | 48 +++++++++++++++++++++++ include/ftxui/screen/screen.hpp | 62 +++--------------------------- src/ftxui/dom/canvas.cpp | 38 +++++++++++++++++- src/ftxui/screen/box.cpp | 6 +++ src/ftxui/screen/image.cpp | 68 +++++++++++++++++++++++++++++++++ src/ftxui/screen/screen.cpp | 48 ++--------------------- 12 files changed, 243 insertions(+), 107 deletions(-) create mode 100644 include/ftxui/screen/image.hpp create mode 100644 include/ftxui/screen/pixel.hpp create mode 100644 src/ftxui/screen/image.cpp diff --git a/.gitignore b/.gitignore index 735ed65..4d12e75 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,10 @@ * !*/ +# Ignore build directories generated by default MSVC CMake integration +# (otherwise causes terribly slow indexing) +out/ + # Allowed top-level files: !.clang-format !.clang-tidy diff --git a/CHANGELOG.md b/CHANGELOG.md index 1246145..be16cc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,9 @@ current (development) reflecting the current scroll position. Proposed by @ibrahimnasson in [issue 752](https://github.com/ArthurSonzogni/FTXUI/issues/752) +### Screen +- Feature: Add `Box::IsEmpty()`. + ### Build - Support for cmake's "unity/jumbo" builds. Fixed by @ClausKlein. diff --git a/CMakeLists.txt b/CMakeLists.txt index 583ed46..65f41db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,11 +33,14 @@ add_library(screen include/ftxui/screen/box.hpp include/ftxui/screen/color.hpp include/ftxui/screen/color_info.hpp + include/ftxui/screen/image.hpp + include/ftxui/screen/pixel.hpp include/ftxui/screen/screen.hpp include/ftxui/screen/string.hpp src/ftxui/screen/box.cpp src/ftxui/screen/color.cpp src/ftxui/screen/color_info.cpp + src/ftxui/screen/image.cpp src/ftxui/screen/screen.cpp src/ftxui/screen/string.cpp src/ftxui/screen/terminal.cpp diff --git a/include/ftxui/dom/canvas.hpp b/include/ftxui/dom/canvas.hpp index 9286838..ffc487c 100644 --- a/include/ftxui/dom/canvas.hpp +++ b/include/ftxui/dom/canvas.hpp @@ -10,7 +10,7 @@ #include // for unordered_map #include "ftxui/screen/color.hpp" // for Color -#include "ftxui/screen/screen.hpp" // for Pixel +#include "ftxui/screen/image.hpp" // for Pixel, Image #ifdef DrawText // Workaround for WinUsr.h (via Windows.h) defining macros that break things. @@ -94,6 +94,12 @@ struct Canvas { void DrawText(int x, int y, const std::string& value); void DrawText(int x, int y, const std::string& value, const Color& color); void DrawText(int x, int y, const std::string& value, const Stylizer& style); + + // Draw using directly pixels or images -------------------------------------- + // x is considered to be a multiple of 2. + // y is considered to be a multiple of 4. + void DrawPixel(int x, int y, const Pixel&); + void DrawImage(int x, int y, const Image&); // Decorator: // x is considered to be a multiple of 2. @@ -104,15 +110,18 @@ struct Canvas { bool IsIn(int x, int y) const { return x >= 0 && x < width_ && y >= 0 && y < height_; } + enum CellType { - kBraille, - kBlock, - kText, + kCell, // Units of size 2x4 + kBlock, // Units of size 2x2 + kBraille, // Units of size 1x1 }; + struct Cell { - CellType type = kText; + CellType type = kCell; Pixel content; }; + struct XY { int x; int y; diff --git a/include/ftxui/screen/box.hpp b/include/ftxui/screen/box.hpp index 701b600..9fc2792 100644 --- a/include/ftxui/screen/box.hpp +++ b/include/ftxui/screen/box.hpp @@ -15,6 +15,7 @@ struct Box { static auto Intersection(Box a, Box b) -> Box; static auto Union(Box a, Box b) -> Box; bool Contain(int x, int y) const; + bool IsEmpty(); bool operator==(const Box& other) const; bool operator!=(const Box& other) const; }; diff --git a/include/ftxui/screen/image.hpp b/include/ftxui/screen/image.hpp new file mode 100644 index 0000000..1790a97 --- /dev/null +++ b/include/ftxui/screen/image.hpp @@ -0,0 +1,50 @@ +// Copyright 2024 Arthur Sonzogni. All rights reserved. +// Use of this source code is governed by the MIT license that can be found in +// the LICENSE file. +#ifndef FTXUI_SCREEN_IMAGE_HPP +#define FTXUI_SCREEN_IMAGE_HPP + +#include // for uint8_t +#include +#include // for string, basic_string, allocator +#include // for vector + +#include "ftxui/screen/box.hpp" // for Box +#include "ftxui/screen/pixel.hpp" // for Pixel + +namespace ftxui { + +/// @brief A rectangular grid of Pixel. +/// @ingroup screen +class Image { + public: + // Constructors: + Image() = delete; + Image(int dimx, int dimy); + + // Access a character in the grid at a given position. + std::string& at(int x, int y); + const std::string& at(int x, int y) const; + + // Access a cell (Pixel) in the grid at a given position. + Pixel& PixelAt(int x, int y); + const Pixel& PixelAt(int x, int y) const; + + // Get screen dimensions. + int dimx() const { return dimx_; } + int dimy() const { return dimy_; } + + // Fill the image with space and default style + void Clear(); + + Box stencil; + + protected: + int dimx_; + int dimy_; + std::vector> pixels_; +}; + +} // namespace ftxui + +#endif // FTXUI_SCREEN_IMAGE_HPP diff --git a/include/ftxui/screen/pixel.hpp b/include/ftxui/screen/pixel.hpp new file mode 100644 index 0000000..0a58a38 --- /dev/null +++ b/include/ftxui/screen/pixel.hpp @@ -0,0 +1,48 @@ +// Copyright 2024 Arthur Sonzogni. All rights reserved. +// Use of this source code is governed by the MIT license that can be found in +// the LICENSE file. + +#include // for uint8_t +#include // for string, basic_string, allocator +#include "ftxui/screen/color.hpp" // for Color, Color::Default + +namespace ftxui { + +/// @brief A Unicode character and its associated style. +/// @ingroup screen +struct Pixel { + Pixel() + : blink(false), + bold(false), + dim(false), + inverted(false), + underlined(false), + underlined_double(false), + strikethrough(false), + automerge(false) {} + + // A bit field representing the style: + bool blink : 1; + bool bold : 1; + bool dim : 1; + bool inverted : 1; + bool underlined : 1; + bool underlined_double : 1; + bool strikethrough : 1; + bool automerge : 1; + + // The hyperlink associated with the pixel. + // 0 is the default value, meaning no hyperlink. + // It's an index for accessing Screen meta data + uint8_t hyperlink = 0; + + // The graphemes stored into the pixel. To support combining characters, + // like: a?, this can potentially contain multiple codepoints. + std::string character = " "; + + // Colors: + Color background_color = Color::Default; + Color foreground_color = Color::Default; +}; + +} // namespace ftxui diff --git a/include/ftxui/screen/screen.hpp b/include/ftxui/screen/screen.hpp index 8d27900..6dd6eed 100644 --- a/include/ftxui/screen/screen.hpp +++ b/include/ftxui/screen/screen.hpp @@ -9,48 +9,12 @@ #include // for string, basic_string, allocator #include // for vector -#include "ftxui/screen/box.hpp" // for Box #include "ftxui/screen/color.hpp" // for Color, Color::Default +#include "ftxui/screen/image.hpp" // for Pixel, Image #include "ftxui/screen/terminal.hpp" // for Dimensions namespace ftxui { -/// @brief A unicode character and its associated style. -/// @ingroup screen -struct Pixel { - Pixel() - : blink(false), - bold(false), - dim(false), - inverted(false), - underlined(false), - underlined_double(false), - strikethrough(false), - automerge(false) {} - - // A bit field representing the style: - bool blink : 1; - bool bold : 1; - bool dim : 1; - bool inverted : 1; - bool underlined : 1; - bool underlined_double : 1; - bool strikethrough : 1; - bool automerge : 1; - - // The hyperlink associated with the pixel. - // 0 is the default value, meaning no hyperlink. - uint8_t hyperlink = 0; - - // The graphemes stored into the pixel. To support combining characters, - // like: a⃦, this can potentially contain multiple codepoints. - std::string character = " "; - - // Colors: - Color background_color = Color::Default; - Color foreground_color = Color::Default; -}; - /// @brief Define how the Screen's dimensions should look like. /// @ingroup screen namespace Dimension { @@ -60,36 +24,24 @@ Dimensions Full(); /// @brief A rectangular grid of Pixel. /// @ingroup screen -class Screen { +class Screen : public Image { public: // Constructors: Screen(int dimx, int dimy); static Screen Create(Dimensions dimension); static Screen Create(Dimensions width, Dimensions height); - // Access a character in the grid at a given position. - std::string& at(int x, int y); - const std::string& at(int x, int y) const; - - // Access a cell (Pixel) in the grid at a given position. - Pixel& PixelAt(int x, int y); - const Pixel& PixelAt(int x, int y) const; - std::string ToString() const; // Print the Screen on to the terminal. void Print() const; - // Get screen dimensions. - int dimx() const { return dimx_; } - int dimy() const { return dimy_; } + // Fill the screen with space and reset any screen state, like hyperlinks, and cursor + void Clear(); // Move the terminal cursor n-lines up with n = dimy(). std::string ResetPosition(bool clear = false) const; - // Fill the screen with space. - void Clear(); - void ApplyShader(); struct Cursor { @@ -107,6 +59,7 @@ class Screen { }; Shape shape; }; + Cursor cursor() const { return cursor_; } void SetCursor(Cursor cursor) { cursor_ = cursor; } @@ -115,12 +68,7 @@ class Screen { uint8_t RegisterHyperlink(const std::string& link); const std::string& Hyperlink(uint8_t id) const; - Box stencil; - protected: - int dimx_; - int dimy_; - std::vector> pixels_; Cursor cursor_; std::vector hyperlinks_ = {""}; }; diff --git a/src/ftxui/dom/canvas.cpp b/src/ftxui/dom/canvas.cpp index 58c2490..81d415b 100644 --- a/src/ftxui/dom/canvas.cpp +++ b/src/ftxui/dom/canvas.cpp @@ -810,13 +810,49 @@ void Canvas::DrawText(int x, continue; } Cell& cell = storage_[XY{x / 2, y / 4}]; - cell.type = CellType::kText; + cell.type = CellType::kCell; cell.content.character = it; style(cell.content); x += 2; } } +/// @brief Directly draw a predefined pixel at the given coordinate +/// @param x the x coordinate of the pixel. +/// @param y the y coordinate of the pixel. +/// @param p the pixel to draw. +void Canvas::DrawPixel(int x, int y, const Pixel& p) { + Cell& cell = storage_[XY{x / 2, y / 4}]; + cell.type = CellType::kCell; + cell.content = p; +} + +/// @brief Draw a predefined image, with top-left corner at the given coordinate +/// You can supply negative coordinates to align the image however you like - +/// only the 'visible' portion will be drawn +/// @param x the x coordinate corresponding to the top-left corner of the image. +/// @param y the y coordinate corresponding to the top-left corner of the image. +/// @param image the image to draw. +void Canvas::DrawImage(int x, int y, const Image& image) { + x /= 2; + y /= 4; + const int dx_begin = std::max(0, -x); + const int dy_begin = std::max(0, -y); + const int dx_end = std::min(image.dimx(), width_ - x); + const int dy_end = std::min(image.dimy(), height_ - y); + + for (int dy = dy_begin; dy < dy_end; ++dy) { + for (int dx = dx_begin; dx < dx_end; ++dx) { + Cell& cell = storage_[XY{ + x + dx, + y + dy, + }]; + cell.type = CellType::kCell; + cell.content = image.PixelAt(dx, dy); + } + } +} + /// @brief Modify a pixel at a given location. /// @param style a function that modifies the pixel. void Canvas::Style(int x, int y, const Stylizer& style) { diff --git a/src/ftxui/screen/box.cpp b/src/ftxui/screen/box.cpp index 38fe484..0210153 100644 --- a/src/ftxui/screen/box.cpp +++ b/src/ftxui/screen/box.cpp @@ -39,6 +39,12 @@ bool Box::Contain(int x, int y) const { y_max >= y; } +/// @return whether the box is empty. +/// @ingroup screen +bool Box::IsEmpty() { + return x_min > x_max || y_min > y_max; +} + /// @return whether |other| is the same as |this| /// @ingroup screen bool Box::operator==(const Box& other) const { diff --git a/src/ftxui/screen/image.cpp b/src/ftxui/screen/image.cpp new file mode 100644 index 0000000..adee91b --- /dev/null +++ b/src/ftxui/screen/image.cpp @@ -0,0 +1,68 @@ +// 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. +#include // for size_t +#include // for operator<<, stringstream, basic_ostream, flush, cout, ostream +#include +#include // for _Rb_tree_const_iterator, map, operator!=, operator== +#include // for allocator, allocator_traits<>::value_type +#include // IWYU pragma: keep +#include // for pair + +#include "ftxui/screen/image.hpp" +#include "ftxui/screen/string.hpp" // for string_width + +namespace ftxui { + +namespace +{ + Pixel& dev_null_pixel() { + static Pixel pixel; + return pixel; + } +} + +Image::Image(int dimx, int dimy) + : stencil{0, dimx - 1, 0, dimy - 1}, + dimx_(dimx), + dimy_(dimy), + pixels_(dimy, std::vector(dimx)) {} + +/// @brief Access a character in a cell at a given position. +/// @param x The cell position along the x-axis. +/// @param y The cell position along the y-axis. +std::string& Image::at(int x, int y) { + return PixelAt(x, y).character; +} + +/// @brief Access a character in a cell at a given position. +/// @param x The cell position along the x-axis. +/// @param y The cell position along the y-axis. +const std::string& Image::at(int x, int y) const { + return PixelAt(x, y).character; +} + +/// @brief Access a cell (Pixel) at a given position. +/// @param x The cell position along the x-axis. +/// @param y The cell position along the y-axis. +Pixel& Image::PixelAt(int x, int y) { + return stencil.Contain(x, y) ? pixels_[y][x] : dev_null_pixel(); +} + +/// @brief Access a cell (Pixel) at a given position. +/// @param x The cell position along the x-axis. +/// @param y The cell position along the y-axis. +const Pixel& Image::PixelAt(int x, int y) const { + return stencil.Contain(x, y) ? pixels_[y][x] : dev_null_pixel(); +} + +/// @brief Clear all the pixel from the screen. +void Image::Clear() { + for (auto& line : pixels_) { + for (auto& cell : line) { + cell = Pixel(); + } + } +} + +} // namespace ftxui diff --git a/src/ftxui/screen/screen.cpp b/src/ftxui/screen/screen.cpp index 2a12219..981a3f6 100644 --- a/src/ftxui/screen/screen.cpp +++ b/src/ftxui/screen/screen.cpp @@ -42,11 +42,6 @@ namespace ftxui { namespace { -Pixel& dev_null_pixel() { - static Pixel pixel; - return pixel; -} - #if defined(_WIN32) void WindowsEmulateVT100Terminal() { static bool done = false; @@ -392,15 +387,11 @@ Screen Screen::Create(Dimensions dimension) { return {dimension.dimx, dimension.dimy}; } -Screen::Screen(int dimx, int dimy) - : stencil{0, dimx - 1, 0, dimy - 1}, - dimx_(dimx), - dimy_(dimy), - pixels_(dimy, std::vector(dimx)) { +Screen::Screen(int dimx, int dimy) : Image{dimx, dimy} { #if defined(_WIN32) // The placement of this call is a bit weird, however we can assume that // anybody who instantiates a Screen object eventually wants to output - // something to the console. + // something to the console. If that is not the case, use an instance of Image instead. // As we require UTF8 for all input/output operations we will just switch to // UTF8 encoding here SetConsoleOutputCP(CP_UTF8); @@ -450,34 +441,6 @@ void Screen::Print() const { std::cout << ToString() << '\0' << std::flush; } -/// @brief Access a character in a cell at a given position. -/// @param x The cell position along the x-axis. -/// @param y The cell position along the y-axis. -std::string& Screen::at(int x, int y) { - return PixelAt(x, y).character; -} - -/// @brief Access a character in a cell at a given position. -/// @param x The cell position along the x-axis. -/// @param y The cell position along the y-axis. -const std::string& Screen::at(int x, int y) const { - return PixelAt(x, y).character; -} - -/// @brief Access a cell (Pixel) at a given position. -/// @param x The cell position along the x-axis. -/// @param y The cell position along the y-axis. -Pixel& Screen::PixelAt(int x, int y) { - return stencil.Contain(x, y) ? pixels_[y][x] : dev_null_pixel(); -} - -/// @brief Access a cell (Pixel) at a given position. -/// @param x The cell position along the x-axis. -/// @param y The cell position along the y-axis. -const Pixel& Screen::PixelAt(int x, int y) const { - return stencil.Contain(x, y) ? pixels_[y][x] : dev_null_pixel(); -} - /// @brief Return a string to be printed in order to reset the cursor position /// to the beginning of the screen. /// @@ -517,11 +480,8 @@ std::string Screen::ResetPosition(bool clear) const { /// @brief Clear all the pixel from the screen. void Screen::Clear() { - for (auto& line : pixels_) { - for (auto& cell : line) { - cell = Pixel(); - } - } + Image::Clear(); + cursor_.x = dimx_ - 1; cursor_.y = dimy_ - 1;