Separate a reusable Image class from Screen (#834)

Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
This commit is contained in:
Dimo Markov 2024-04-27 12:03:44 +03:00 committed by GitHub
parent 1f6e1101e8
commit 293ff179f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 243 additions and 107 deletions

4
.gitignore vendored
View File

@ -3,6 +3,10 @@
* *
!*/ !*/
# Ignore build directories generated by default MSVC CMake integration
# (otherwise causes terribly slow indexing)
out/
# Allowed top-level files: # Allowed top-level files:
!.clang-format !.clang-format
!.clang-tidy !.clang-tidy

View File

@ -32,6 +32,9 @@ current (development)
reflecting the current scroll position. Proposed by @ibrahimnasson in reflecting the current scroll position. Proposed by @ibrahimnasson in
[issue 752](https://github.com/ArthurSonzogni/FTXUI/issues/752) [issue 752](https://github.com/ArthurSonzogni/FTXUI/issues/752)
### Screen
- Feature: Add `Box::IsEmpty()`.
### Build ### Build
- Support for cmake's "unity/jumbo" builds. Fixed by @ClausKlein. - Support for cmake's "unity/jumbo" builds. Fixed by @ClausKlein.

View File

@ -33,11 +33,14 @@ add_library(screen
include/ftxui/screen/box.hpp include/ftxui/screen/box.hpp
include/ftxui/screen/color.hpp include/ftxui/screen/color.hpp
include/ftxui/screen/color_info.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/screen.hpp
include/ftxui/screen/string.hpp include/ftxui/screen/string.hpp
src/ftxui/screen/box.cpp src/ftxui/screen/box.cpp
src/ftxui/screen/color.cpp src/ftxui/screen/color.cpp
src/ftxui/screen/color_info.cpp src/ftxui/screen/color_info.cpp
src/ftxui/screen/image.cpp
src/ftxui/screen/screen.cpp src/ftxui/screen/screen.cpp
src/ftxui/screen/string.cpp src/ftxui/screen/string.cpp
src/ftxui/screen/terminal.cpp src/ftxui/screen/terminal.cpp

View File

@ -10,7 +10,7 @@
#include <unordered_map> // for unordered_map #include <unordered_map> // for unordered_map
#include "ftxui/screen/color.hpp" // for Color #include "ftxui/screen/color.hpp" // for Color
#include "ftxui/screen/screen.hpp" // for Pixel #include "ftxui/screen/image.hpp" // for Pixel, Image
#ifdef DrawText #ifdef DrawText
// Workaround for WinUsr.h (via Windows.h) defining macros that break things. // 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);
void DrawText(int x, int y, const std::string& value, const Color& color); 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); 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: // Decorator:
// x is considered to be a multiple of 2. // x is considered to be a multiple of 2.
@ -104,15 +110,18 @@ struct Canvas {
bool IsIn(int x, int y) const { bool IsIn(int x, int y) const {
return x >= 0 && x < width_ && y >= 0 && y < height_; return x >= 0 && x < width_ && y >= 0 && y < height_;
} }
enum CellType { enum CellType {
kBraille, kCell, // Units of size 2x4
kBlock, kBlock, // Units of size 2x2
kText, kBraille, // Units of size 1x1
}; };
struct Cell { struct Cell {
CellType type = kText; CellType type = kCell;
Pixel content; Pixel content;
}; };
struct XY { struct XY {
int x; int x;
int y; int y;

View File

@ -15,6 +15,7 @@ struct Box {
static auto Intersection(Box a, Box b) -> Box; static auto Intersection(Box a, Box b) -> Box;
static auto Union(Box a, Box b) -> Box; static auto Union(Box a, Box b) -> Box;
bool Contain(int x, int y) const; bool Contain(int x, int y) const;
bool IsEmpty();
bool operator==(const Box& other) const; bool operator==(const Box& other) const;
bool operator!=(const Box& other) const; bool operator!=(const Box& other) const;
}; };

View File

@ -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 <cstdint> // for uint8_t
#include <memory>
#include <string> // for string, basic_string, allocator
#include <vector> // 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<std::vector<Pixel>> pixels_;
};
} // namespace ftxui
#endif // FTXUI_SCREEN_IMAGE_HPP

View File

@ -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 <cstdint> // for uint8_t
#include <string> // 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

View File

@ -9,48 +9,12 @@
#include <string> // for string, basic_string, allocator #include <string> // for string, basic_string, allocator
#include <vector> // for vector #include <vector> // for vector
#include "ftxui/screen/box.hpp" // for Box
#include "ftxui/screen/color.hpp" // for Color, Color::Default #include "ftxui/screen/color.hpp" // for Color, Color::Default
#include "ftxui/screen/image.hpp" // for Pixel, Image
#include "ftxui/screen/terminal.hpp" // for Dimensions #include "ftxui/screen/terminal.hpp" // for Dimensions
namespace ftxui { 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. /// @brief Define how the Screen's dimensions should look like.
/// @ingroup screen /// @ingroup screen
namespace Dimension { namespace Dimension {
@ -60,36 +24,24 @@ Dimensions Full();
/// @brief A rectangular grid of Pixel. /// @brief A rectangular grid of Pixel.
/// @ingroup screen /// @ingroup screen
class Screen { class Screen : public Image {
public: public:
// Constructors: // Constructors:
Screen(int dimx, int dimy); Screen(int dimx, int dimy);
static Screen Create(Dimensions dimension); static Screen Create(Dimensions dimension);
static Screen Create(Dimensions width, Dimensions height); 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; std::string ToString() const;
// Print the Screen on to the terminal. // Print the Screen on to the terminal.
void Print() const; void Print() const;
// Get screen dimensions. // Fill the screen with space and reset any screen state, like hyperlinks, and cursor
int dimx() const { return dimx_; } void Clear();
int dimy() const { return dimy_; }
// Move the terminal cursor n-lines up with n = dimy(). // Move the terminal cursor n-lines up with n = dimy().
std::string ResetPosition(bool clear = false) const; std::string ResetPosition(bool clear = false) const;
// Fill the screen with space.
void Clear();
void ApplyShader(); void ApplyShader();
struct Cursor { struct Cursor {
@ -107,6 +59,7 @@ class Screen {
}; };
Shape shape; Shape shape;
}; };
Cursor cursor() const { return cursor_; } Cursor cursor() const { return cursor_; }
void SetCursor(Cursor cursor) { cursor_ = cursor; } void SetCursor(Cursor cursor) { cursor_ = cursor; }
@ -115,12 +68,7 @@ class Screen {
uint8_t RegisterHyperlink(const std::string& link); uint8_t RegisterHyperlink(const std::string& link);
const std::string& Hyperlink(uint8_t id) const; const std::string& Hyperlink(uint8_t id) const;
Box stencil;
protected: protected:
int dimx_;
int dimy_;
std::vector<std::vector<Pixel>> pixels_;
Cursor cursor_; Cursor cursor_;
std::vector<std::string> hyperlinks_ = {""}; std::vector<std::string> hyperlinks_ = {""};
}; };

View File

@ -810,13 +810,49 @@ void Canvas::DrawText(int x,
continue; continue;
} }
Cell& cell = storage_[XY{x / 2, y / 4}]; Cell& cell = storage_[XY{x / 2, y / 4}];
cell.type = CellType::kText; cell.type = CellType::kCell;
cell.content.character = it; cell.content.character = it;
style(cell.content); style(cell.content);
x += 2; 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. /// @brief Modify a pixel at a given location.
/// @param style a function that modifies the pixel. /// @param style a function that modifies the pixel.
void Canvas::Style(int x, int y, const Stylizer& style) { void Canvas::Style(int x, int y, const Stylizer& style) {

View File

@ -39,6 +39,12 @@ bool Box::Contain(int x, int y) const {
y_max >= y; 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| /// @return whether |other| is the same as |this|
/// @ingroup screen /// @ingroup screen
bool Box::operator==(const Box& other) const { bool Box::operator==(const Box& other) const {

View File

@ -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 <cstdint> // for size_t
#include <iostream> // for operator<<, stringstream, basic_ostream, flush, cout, ostream
#include <limits>
#include <map> // for _Rb_tree_const_iterator, map, operator!=, operator==
#include <memory> // for allocator, allocator_traits<>::value_type
#include <sstream> // IWYU pragma: keep
#include <utility> // 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<Pixel>(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

View File

@ -42,11 +42,6 @@ namespace ftxui {
namespace { namespace {
Pixel& dev_null_pixel() {
static Pixel pixel;
return pixel;
}
#if defined(_WIN32) #if defined(_WIN32)
void WindowsEmulateVT100Terminal() { void WindowsEmulateVT100Terminal() {
static bool done = false; static bool done = false;
@ -392,15 +387,11 @@ Screen Screen::Create(Dimensions dimension) {
return {dimension.dimx, dimension.dimy}; return {dimension.dimx, dimension.dimy};
} }
Screen::Screen(int dimx, int dimy) Screen::Screen(int dimx, int dimy) : Image{dimx, dimy} {
: stencil{0, dimx - 1, 0, dimy - 1},
dimx_(dimx),
dimy_(dimy),
pixels_(dimy, std::vector<Pixel>(dimx)) {
#if defined(_WIN32) #if defined(_WIN32)
// The placement of this call is a bit weird, however we can assume that // The placement of this call is a bit weird, however we can assume that
// anybody who instantiates a Screen object eventually wants to output // 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 // As we require UTF8 for all input/output operations we will just switch to
// UTF8 encoding here // UTF8 encoding here
SetConsoleOutputCP(CP_UTF8); SetConsoleOutputCP(CP_UTF8);
@ -450,34 +441,6 @@ void Screen::Print() const {
std::cout << ToString() << '\0' << std::flush; 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 /// @brief Return a string to be printed in order to reset the cursor position
/// to the beginning of the screen. /// 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. /// @brief Clear all the pixel from the screen.
void Screen::Clear() { void Screen::Clear() {
for (auto& line : pixels_) { Image::Clear();
for (auto& cell : line) {
cell = Pixel();
}
}
cursor_.x = dimx_ - 1; cursor_.x = dimx_ - 1;
cursor_.y = dimy_ - 1; cursor_.y = dimy_ - 1;