目录
案例实践:自定义ListView部件,实现显示一个文本列表显示
requirement.hpp
Requirement类,定义了计算布局相关的参数。
struct Requirement { // 定义布局相关的参数
// 完全绘制元素所需的最小尺寸。
int min_x = 0;
int min_y = 0;
// 当一个父节点的Box边界空间存放所有子节点后仍然存在剩余空间时
// grow参数会存储所有同级别的节点需要增加渲染区域的比重,也就是占用父节点剩余空间的比重
int flex_grow_x = 0;
int flex_grow_y = 0;
// 当一个父节点的Box边界空间存放所有子节点后发现空间不够时
// shrink参数会存储所有同级别的节点需要收缩渲染区域的比重
int flex_shrink_x = 0;
int flex_shrink_y = 0;
// 焦点管理支持 frame/focus/select 元素。
struct Focused {
bool enabled = false; // 是否开启焦点
Box box; // 焦点矩形区域
Node* node = nullptr; // 渲染节点
Screen::Cursor::Shape cursor_shape = Screen::Cursor::Shape::Hidden;//光标样式
// 用于与组件交互的内部。
bool component_active = false;
// 返回该要求是否应优先于其他要求。
bool Prefer(const Focused& other) const {
if (!other.enabled) {
return false;
}
if (!enabled) {
return true;
}
return other.component_active && !component_active;
}
};
Focused focused; // 存储焦点状态
};
关于flex_grow参数和flex_shrink参数的具体介绍,详解 flex-grow 与 flex-shrink - 知乎
selection.hpp
Selection类
定义了选择区域信息。
class Selection {
public:
Selection(); // 默认无选择信息
Selection(int start_x, int start_y, int end_x, int end_y);// 选择区域信息
const Box& GetBox() const; // 获取选择矩形区域Box
// 将选择内容饱和到框内, 这由“hbox”调用以将选择传播给其子项。
Selection SaturateHorizontal(Box box);
// 将选择内容饱和到框内, 这由“vbox”调用以将选择传播给其子项。
Selection SaturateVertical(Box box);
// 判断选择区域是否为空
bool IsEmpty() const { return empty_; }
// 添加部分区域的内容
void AddPart(const std::string& part, int y, int left, int right);
// 获取部分区域的内容
std::string GetParts() { return parts_.str(); }
private:
Selection(int start_x, int start_y, int end_x, int end_y, Selection* parent);
const int start_x_ = 0;
const int start_y_ = 0;
const int end_x_ = 0;
const int end_y_ = 0;
const Box box_ = {};
Selection* const parent_ = this;
const bool empty_ = true;
std::stringstream parts_;
// 最后插入部分的位置。
int x_ = 0;
int y_ = 0;
};
node.hpp
Node基类
用于描述节点,节点下可以存在多个子节点。通过这样的节点嵌套实现dom树。
class Node {
public:
Node();
explicit Node(Elements children);
Node(const Node&) = delete;
Node(const Node&&) = delete;
Node& operator=(const Node&) = delete;
Node& operator=(const Node&&) = delete;
virtual ~Node();
// 1、计算当前节点下的所有子节点的布局参数和渲染区域参数。
virtual void ComputeRequirement();
// 返回当前节点的布局参数、渲染区域信息。告知父元素该元素所需的尺寸。从子元素传播到父元素。
Requirement requirement() { return requirement_; }
// 2、指定当前节点的最终显示区域尺寸。从父元素传播到子元素。
virtual void SetBox(Box box);
// 3、(可选)选择。从父节点传播到子节点。
virtual void Select(Selection& selection);
// 4、将当前节点下所有的子节点内容渲染到Screen中
virtual void Render(Screen& screen);
// 获取当前选中的内容
virtual std::string GetSelectedContent(Selection& selection);
// 根节点通过设置多次迭代,完成所有子节点的计算工作
struct Status {
int iteration = 0;
bool need_iteration = false;
};
virtual void Check(Status* status);
// 渲染选中的节点内容渲染到Screen中
friend void Render(Screen& screen, Node* node, Selection& selection);
protected:
Elements children_; //存储当前节点的所有子节点
Requirement requirement_; // 存储当前节点的布局信息
Box box_;// 存储当前节点最终的绘制大小
};
void Render(Screen& screen, const Element& element);//内部调用渲染Node树的操作
void Render(Screen& screen, Node* node);//渲染Node树操作
void Render(Screen& screen, Node* node, Selection& selection);//渲染选中的节点内容
std::string GetNodeSelectedContent(Screen& screen,
Node* node,
Selection& selection);// 获取选中的节点内容
dom树渲染流程(渲染Node树)
下面这个函数定义了渲染Node树到Screen绘制区域所需的流程。
// 在Screen渲染一个Element
void Render(Screen& screen, const Element& element) {
Selection selection;
Render(screen, element.get(), selection);
}
// 在Screen渲染一个节点
void Render(Screen& screen, Node* node) {
Selection selection;
Render(screen, node, selection);
}
// 实现渲染Node树到Screen绘制区域的具体操作
void Render(Screen& screen, Node* node, Selection& selection) {
//获取Screen显示区域的尺寸
Box box;
box.x_min = 0;
box.y_min = 0;
box.x_max = screen.dimx() - 1;
box.y_max = screen.dimy() - 1;
Node::Status status; // 创建状态参数,用于判断Node树是否布局计算完成
node->Check(&status); // 将状态传递给Node树的所有节点
const int max_iterations = 20; //定义布局计算的最大次数
while (status.need_iteration && status.iteration < max_iterations) {
// Step 1: 递归调用Node树,完成布局参数和渲染区域的计算操作
node->ComputeRequirement();
// Step 2: 设置根节点的显示区域大小
node->SetBox(box);
// 检查元素是否需要另一次布局算法迭代。
status.need_iteration = false;
status.iteration++;
// 递归调用Node树,完成所有节点的的检查工作
node->Check(&status);
}
// Step 3: 获取选择的节点
if (!selection.IsEmpty()) {
node->Select(selection);
}
// 检查是否开启焦点并且光标样式未隐藏
if (node->requirement().focused.enabled
#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
&&
node->requirement().focused.cursor_shape != Screen::Cursor::Shape::Hidden
#endif
) {
// 设置要渲染的光标信息
screen.SetCursor(Screen::Cursor{
node->requirement().focused.node->box_.x_max,
node->requirement().focused.node->box_.y_max,
node->requirement().focused.cursor_shape,
});
} else {
// 设置空的光标信息并隐藏光标样式
screen.SetCursor(Screen::Cursor{
screen.dimx() - 1,
screen.dimy() - 1,
Screen::Cursor::Shape::Hidden,
});
}
// Step 4: 递归调用Node树,完成所有节点的渲染操作
screen.stencil = box;
node->Render(screen);
// Step 5: 完成Screen绘制区域内多个Pixel显示单元的字符合并、显示优化操作
screen.ApplyShader();
}
// 获取选中的节点内容
std::string GetNodeSelectedContent(Screen& screen,
Node* node,
Selection& selection) {
//获取Screen显示区域的尺寸
Box box;
box.x_min = 0;
box.y_min = 0;
box.x_max = screen.dimx() - 1;
box.y_max = screen.dimy() - 1;
Node::Status status; // 创建状态参数,用于判断Node树是否布局计算完成
node->Check(&status); // 将状态传递给Node树的所有节点
const int max_iterations = 20; //定义布局计算的最大次数
while (status.need_iteration && status.iteration < max_iterations) {
// Step 1: 递归调用Node树,完成布局参数和渲染区域的计算操作
node->ComputeRequirement();
// Step 2: 设置根节点的显示区域大小
node->SetBox(box);
// 检查元素是否需要另一次布局算法迭代。
status.need_iteration = false;
status.iteration++;
// 递归调用Node树,完成所有节点的的检查工作
node->Check(&status);
}
// Step 3: 获取选择的节点
node->Select(selection);
// Step 4: 获取选定的内容。
return node->GetSelectedContent(selection);
}
flexbox_config.hpp
FlexboxConfig类
定义flex布局的属性参数。
struct FlexboxConfig {
enum class Direction {
Row, ///< Flex items排成一排。
RowInversed, ///< Flex items以行的形式排列,但顺序相反。
Column, ///< Flex items按列排列。
ColumnInversed ///< Flex items按列排列,但顺序相反。
};
// 存储主轴,从而定义了item在flex容器中的放置方向。
// Flexbox(除了包裹之外)是一个单向布局概念。flex item可以主要以水平行或垂直列的形式布局。
Direction direction = Direction::Row;
enum class Wrap {
NoWrap, ///< Flex items将尝试全部容纳在一行中。
Wrap, ///< Flex items将会换行到多行。
WrapInversed, ///< Flex items将会换行到多行,但顺序相反。
};
// 默认情况下,所有 flex 元素都会尝试放在一行中。
// 你可以使用此属性更改此设置,并允许元素根据需要换行。
Wrap wrap = Wrap::Wrap;
enum class JustifyContent {
FlexStart, // Items与flexbox方向的起点对齐。
FlexEnd, // Items与flexbox方向的末端对齐。
Center, // Items沿线居中放置。
Stretch, // Items被拉伸以填满线路。
SpaceBetween, // Items均匀分布在行中;第一个Items在起始行,最后一个Items在结束行
SpaceAround, // 元素在行内均匀分布,周围间距相等。
// 请注意,视觉上间距并不相等,因为所有元素在两侧的间距都相等。
// 第一个元素相对于容器边缘有一个单位的间距,但与下一个
// 元素之间有两个单位的间距,因为下一个元素有其自己的间距。
SpaceEvenly, // Items的分布使得任意两个Items之间的间距(以及到边缘的空间)相等。
};
// 定义沿主轴的对齐方式。
// 当一行上的所有弹性项目都不可伸缩,或者虽然可伸缩但已达到其最大尺寸
// 时,它有助于分配剩余的可用空间。当弹性项目溢出行时,它还能对项目的对齐方式进行一些控制。
JustifyContent justify_content = JustifyContent::FlexStart;
// 这定义了flex items在当前行沿横轴的默认布局行为。
// 可以将其视为横轴(垂直于主轴)的 justify-content 版本。
enum class AlignItems {
FlexStart, ///< items放置在横轴的起点处。
FlexEnd, ///< items放置在横轴的末端。
Center, ///< items沿横轴居中。
Stretch, ///< items被拉伸以填充横轴。
};
AlignItems align_items = AlignItems::FlexStart;
// 当横轴上有额外空间时,这将对齐弹性容器内的线条,
// 类似于 justify-content 如何对齐主轴内的各个项目。
enum class AlignContent {
FlexStart, ///< Items放置在横轴的起点处。
FlexEnd, ///< Items放置在横轴的末端。
Center, ///< Items沿横轴居中。
Stretch, ///< Items被拉伸以填充横轴。
SpaceBetween, ///< Items均匀分布在横轴上。
SpaceAround, ///< Items均匀分布,每行周围有相等的空间。
SpaceEvenly, ///< Items均匀分布在横轴上,周围有相等的空间。
};
AlignContent align_content = AlignContent::FlexStart;
int gap_x = 0;
int gap_y = 0;
// 构造函数模式。链式使用如下:
// ```
// FlexboxConfig()
// .Set(FlexboxConfig::Direction::Row)
// .Set(FlexboxConfig::Wrap::Wrap);
// ```
FlexboxConfig& Set(FlexboxConfig::Direction);
FlexboxConfig& Set(FlexboxConfig::Wrap);
FlexboxConfig& Set(FlexboxConfig::JustifyContent);
FlexboxConfig& Set(FlexboxConfig::AlignItems);
FlexboxConfig& Set(FlexboxConfig::AlignContent);
FlexboxConfig& SetGap(int gap_x, int gap_y);
};
linear_gradient.hpp
LinearGradient类
定义了线性渐变的属性参数。
struct LinearGradient {
float angle = 0.f;
struct Stop {
Color color = Color::Default;
std::optional<float> position;
};
std::vector<Stop> stops;
// 简单构造函数
LinearGradient();
LinearGradient(Color begin, Color end);
LinearGradient(float angle, Color begin, Color end);
// 使用构建器模式的修饰符。
LinearGradient& Angle(float angle);
LinearGradient& Stop(Color color, float position);
LinearGradient& Stop(Color color);
};
table.hpp
Table类
定义了Table的容器元素特性。
class Table {
public:
Table();
explicit Table(std::vector<std::vector<std::string>>);
explicit Table(std::vector<std::vector<Element>>);
Table(std::initializer_list<std::vector<std::string>> init);
TableSelection SelectAll();
TableSelection SelectCell(int column, int row);
TableSelection SelectRow(int row_index);
TableSelection SelectRows(int row_min, int row_max);
TableSelection SelectColumn(int column_index);
TableSelection SelectColumns(int column_min, int column_max);
TableSelection SelectRectangle(int column_min,
int column_max,
int row_min,
int row_max);
Element Render();
private:
void Initialize(std::vector<std::vector<Element>>);
friend TableSelection;
std::vector<std::vector<Element>> elements_;
int input_dim_x_ = 0;
int input_dim_y_ = 0;
int dim_x_ = 0;
int dim_y_ = 0;
};
class TableSelection {
public:
void Decorate(Decorator);
void DecorateAlternateRow(Decorator, int modulo = 2, int shift = 0);
void DecorateAlternateColumn(Decorator, int modulo = 2, int shift = 0);
void DecorateCells(Decorator);
void DecorateCellsAlternateColumn(Decorator, int modulo = 2, int shift = 0);
void DecorateCellsAlternateRow(Decorator, int modulo = 2, int shift = 0);
void Border(BorderStyle border = LIGHT);
void BorderLeft(BorderStyle border = LIGHT);
void BorderRight(BorderStyle border = LIGHT);
void BorderTop(BorderStyle border = LIGHT);
void BorderBottom(BorderStyle border = LIGHT);
void Separator(BorderStyle border = LIGHT);
void SeparatorVertical(BorderStyle border = LIGHT);
void SeparatorHorizontal(BorderStyle border = LIGHT);
private:
friend Table;
Table* table_;
int x_min_;
int x_max_;
int y_min_;
int y_max_;
};
canvas.hpp
Canvas类
定义了画布的元素。
struct Canvas {
public:
Canvas() = default;
Canvas(int width, int height);
// Getters:
int width() const { return width_; }
int height() const { return height_; }
Pixel GetPixel(int x, int y) const;
using Stylizer = std::function<void(Pixel&)>;
// Draws using braille characters --------------------------------------------
void DrawPointOn(int x, int y);
void DrawPointOff(int x, int y);
void DrawPointToggle(int x, int y);
void DrawPoint(int x, int y, bool value);
void DrawPoint(int x, int y, bool value, const Stylizer& s);
void DrawPoint(int x, int y, bool value, const Color& color);
void DrawPointLine(int x1, int y1, int x2, int y2);
void DrawPointLine(int x1, int y1, int x2, int y2, const Stylizer& s);
void DrawPointLine(int x1, int y1, int x2, int y2, const Color& color);
void DrawPointCircle(int x, int y, int radius);
void DrawPointCircle(int x, int y, int radius, const Stylizer& s);
void DrawPointCircle(int x, int y, int radius, const Color& color);
void DrawPointCircleFilled(int x, int y, int radius);
void DrawPointCircleFilled(int x, int y, int radius, const Stylizer& s);
void DrawPointCircleFilled(int x, int y, int radius, const Color& color);
void DrawPointEllipse(int x, int y, int r1, int r2);
void DrawPointEllipse(int x, int y, int r1, int r2, const Color& color);
void DrawPointEllipse(int x, int y, int r1, int r2, const Stylizer& s);
void DrawPointEllipseFilled(int x, int y, int r1, int r2);
void DrawPointEllipseFilled(int x, int y, int r1, int r2, const Color& color);
void DrawPointEllipseFilled(int x, int y, int r1, int r2, const Stylizer& s);
// Draw using box characters -------------------------------------------------
// Block are of size 1x2. y is considered to be a multiple of 2.
void DrawBlockOn(int x, int y);
void DrawBlockOff(int x, int y);
void DrawBlockToggle(int x, int y);
void DrawBlock(int x, int y, bool value);
void DrawBlock(int x, int y, bool value, const Stylizer& s);
void DrawBlock(int x, int y, bool value, const Color& color);
void DrawBlockLine(int x1, int y1, int x2, int y2);
void DrawBlockLine(int x1, int y1, int x2, int y2, const Stylizer& s);
void DrawBlockLine(int x1, int y1, int x2, int y2, const Color& color);
void DrawBlockCircle(int x1, int y1, int radius);
void DrawBlockCircle(int x1, int y1, int radius, const Stylizer& s);
void DrawBlockCircle(int x1, int y1, int radius, const Color& color);
void DrawBlockCircleFilled(int x1, int y1, int radius);
void DrawBlockCircleFilled(int x1, int y1, int radius, const Stylizer& s);
void DrawBlockCircleFilled(int x1, int y1, int radius, const Color& color);
void DrawBlockEllipse(int x1, int y1, int r1, int r2);
void DrawBlockEllipse(int x1, int y1, int r1, int r2, const Stylizer& s);
void DrawBlockEllipse(int x1, int y1, int r1, int r2, const Color& color);
void DrawBlockEllipseFilled(int x1, int y1, int r1, int r2);
void DrawBlockEllipseFilled(int x1,
int y1,
int r1,
int r2,
const Stylizer& s);
void DrawBlockEllipseFilled(int x1,
int y1,
int r1,
int r2,
const Color& color);
// Draw using normal characters ----------------------------------------------
// Draw using character of size 2x4 at position (x,y)
// x is considered to be a multiple of 2.
// y is considered to be a multiple of 4.
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.
// y is considered to be a multiple of 4.
void Style(int x, int y, const Stylizer& style);
private:
bool IsIn(int x, int y) const {
return x >= 0 && x < width_ && y >= 0 && y < height_;
}
enum CellType {
kCell, // Units of size 2x4
kBlock, // Units of size 2x2
kBraille, // Units of size 1x1
};
struct Cell {
CellType type = kCell;
Pixel content;
};
struct XY {
int x;
int y;
bool operator==(const XY& other) const {
return x == other.x && y == other.y;
}
};
struct XYHash {
size_t operator()(const XY& xy) const {
constexpr size_t shift = 1024;
return size_t(xy.x) * shift + size_t(xy.y);
}
};
int width_ = 0;
int height_ = 0;
std::unordered_map<XY, Cell, XYHash> storage_;
};
elements.hpp
定义了预先设计好的element类型,我们可以通过预先设计好的element类型的各种组合,实现我们想要的界面。
class Node;
// 定义Element为节点智能指针
using Element = std::shared_ptr<Node>;
// 定义Elements为节点数组
using Elements = std::vector<Element>;
// 定义Decorator为回调函数
using Decorator = std::function<Element(Element)>;
// 定义GraphFunction为回调函数
using GraphFunction = std::function<std::vector<int>(int, int)>;
// 定义Border样式
enum BorderStyle {
LIGHT,
DASHED,
HEAVY,
DOUBLE,
ROUNDED,
EMPTY,
};
// 定义Element的装饰操作
Element operator|(Element, Decorator);
Element& operator|=(Element&, Decorator);
Elements operator|(Elements, Decorator);
Decorator operator|(Decorator, Decorator);
// 定义 Widget 显示部件
Element text(std::string text);
Element vtext(std::string text);
Element separator();
Element separatorLight();
Element separatorDashed();
Element separatorHeavy();
Element separatorDouble();
Element separatorEmpty();
Element separatorStyled(BorderStyle);
Element separator(Pixel);
Element separatorCharacter(std::string);
Element separatorHSelector(float left,
float right,
Color unselected_color,
Color selected_color);
Element separatorVSelector(float up,
float down,
Color unselected_color,
Color selected_color);
Element gauge(float progress);
Element gaugeLeft(float progress);
Element gaugeRight(float progress);
Element gaugeUp(float progress);
Element gaugeDown(float progress);
Element gaugeDirection(float progress, Direction direction);
Element border(Element);
Element borderLight(Element);
Element borderDashed(Element);
Element borderHeavy(Element);
Element borderDouble(Element);
Element borderRounded(Element);
Element borderEmpty(Element);
Decorator borderStyled(BorderStyle);
Decorator borderStyled(BorderStyle, Color);
Decorator borderStyled(Color);
Decorator borderWith(const Pixel&);
Element window(Element title, Element content, BorderStyle border = ROUNDED);
Element spinner(int charset_index, size_t image_index);
Element paragraph(const std::string& text);
Element paragraphAlignLeft(const std::string& text);
Element paragraphAlignRight(const std::string& text);
Element paragraphAlignCenter(const std::string& text);
Element paragraphAlignJustify(const std::string& text);
Element graph(GraphFunction);
Element emptyElement();
Element canvas(ConstRef<Canvas>);
Element canvas(int width, int height, std::function<void(Canvas&)>);
Element canvas(std::function<void(Canvas&)>);
// 定义 Decorator 装饰器
Element bold(Element);
Element dim(Element);
Element italic(Element);
Element inverted(Element);
Element underlined(Element);
Element underlinedDouble(Element);
Element blink(Element);
Element strikethrough(Element);
Decorator color(Color);
Decorator bgcolor(Color);
Decorator color(const LinearGradient&);
Decorator bgcolor(const LinearGradient&);
Element color(Color, Element);
Element bgcolor(Color, Element);
Element color(const LinearGradient&, Element);
Element bgcolor(const LinearGradient&, Element);
Decorator focusPosition(int x, int y);
Decorator focusPositionRelative(float x, float y);
Element automerge(Element child);
Decorator hyperlink(std::string link);
Element hyperlink(std::string link, Element child);
Element selectionStyleReset(Element);
Decorator selectionColor(Color foreground);
Decorator selectionBackgroundColor(Color foreground);
Decorator selectionForegroundColor(Color foreground);
Decorator selectionStyle(std::function<void(Pixel&)> style);
// 定义 Layout 布局
// 水平、垂直或堆叠的元素集。
Element hbox(Elements);
Element vbox(Elements);
Element dbox(Elements);
Element flexbox(Elements, FlexboxConfig config = FlexboxConfig());
Element gridbox(std::vector<Elements> lines);
Element hflow(Elements); // Helper: default flexbox with row direction.
Element vflow(Elements); // Helper: default flexbox with column direction.
// 定义 Flexibility 属性
// 定义当容器内剩余空间未被全部使用时如何共享剩余空间。
Element flex(Element); // 如果可能/需要,则展开/最小化。
Element flex_grow(Element); // 如果可能的话,扩展元素。
Element flex_shrink(Element); // 如果需要,最小化元素。
Element xflex(Element); // 如果可能/需要,则在 X 轴上展开/最小化。
Element xflex_grow(Element); // 如果可能的话,在 X 轴上展开元素。
Element xflex_shrink(Element); // 如果需要,最小化 X 轴上的元素。
Element yflex(Element); // 如果可能/需要,则在 Y 轴上展开/最小化。
Element yflex_grow(Element); // 如果可能的话,在 Y 轴上展开元素。
Element yflex_shrink(Element); // 如果需要,最小化 Y 轴上的元素。
Element notflex(Element); // 重置 flex 属性。
Element filler(); // 空白的可扩展元素。
// 定义 Size 重载;
enum WidthOrHeight { WIDTH, HEIGHT };
enum Constraint { LESS_THAN, EQUAL, GREATER_THAN };
Decorator size(WidthOrHeight, Constraint, int value);
// 定义 Frame
// 框架是一个可滚动的区域。其内部区域可能大于外部区域。滚动内部区域是为了使焦点元素可见。
Element frame(Element);
Element xframe(Element);
Element yframe(Element);
Element focus(Element);
Element select(Element e); // 已弃用 - 焦点的别名。
// 定义 Cursor 焦点
// 这些类似于“焦点”,但也会改变光标的形状。
Element focusCursorBlock(Element);
Element focusCursorBlockBlinking(Element);
Element focusCursorBar(Element);
Element focusCursorBarBlinking(Element);
Element focusCursorUnderline(Element);
Element focusCursorUnderlineBlinking(Element);
// 定义 Misc 杂项
Element vscroll_indicator(Element);
Element hscroll_indicator(Element);
Decorator reflect(Box& box);
// 在绘制|元素|之前,清除下方的像素。这在与dbox结合使用时很有用。
Element clear_under(Element element);
// 定义 Util 工具
Element hcenter(Element);
Element vcenter(Element);
Element center(Element);
Element align_right(Element);
Element nothing(Element element);
// 填充绘制区域
namespace Dimension {
Dimensions Fit(Element&, bool extend_beyond_screen = false);
} // namespace Dimension
以上是elements.hpp的源码。
Widget 部件
用于提供基本的显示功能,在创建一个widget元素时,都是使用对应的函数来创建对象的,然后返回一个基类指针类型。例如下面这个,创建text元素使用text函数创建,然后返回一个Element类型。
// 在elements.hpp文件中声明
Element text(std::string text);
// 在text.cpp文件中定义
class Text : public Node {
public:
explicit Text(std::string text) : text_(std::move(text)) {}
...
};
// 构造 text 渲染节点
Element text(std::string text) {
return std::make_shared<Text>(std::move(text));
}
Decorator装饰器
Decorator与普通的Element类型一样,但是通过重载方法实现一些特殊的用法,将Element作为子节点进行包装后渲染:
// 在elements.hpp中定义
Element operator|(Element, Decorator);
Element& operator|=(Element&, Decorator);
Elements operator|(Elements, Decorator);
Decorator operator|(Decorator, Decorator);
// 实现Element元素与Decorator装饰器组合使用的方式,例如: text() | bold
Element operator|(Element element, Decorator decorator) { // NOLINT
return decorator(std::move(element));
}
// 语法同上边一样
Element& operator|=(Element& e, Decorator d) {
e = e | std::move(d);
return e;
}
// 实现Elements容器与Decorator装饰器组合使用的方式,例如: hbox() | bold
Elements operator|(Elements elements, Decorator decorator) { // NOLINT
Elements output;
for (auto& it : elements) {
output.push_back(std::move(it) | decorator);
}
return output;
}
// 实现Decorator装饰器与Decorator装饰器组合使用的方式,例如: inverted | bold
Decorator operator|(Decorator a, Decorator b) {
return compose(std::move(a), //
std::move(b));
}
Layout布局管理器
Layout布局管理器的构造函数一般是通过接收一个Elements类型,然后Layout布局管理器自身最为一个Element,将接收的Elements所有子节点进行包装后渲染。例如下面这个:
// 在elements.hpp文件中声明
Element hbox(Elements);
// 在hbox.cpp文件中定义
class HBox : public Node {
public:
explicit HBox(Elements children) : Node(std::move(children)) {}
...
};
// 构造 hbox 渲染节点
Element hbox(Elements children) {
return std::make_shared<HBox>(std::move(children));
}
Flexibility属性
与包装器类似,用于包装Element元素后渲染。
// 在 element.h 声明
Element flex(Element);
// 在 flex.cpp 定义
class Flex : public Node {
public:
explicit Flex(FlexFunction f) : f_(f) {}
Flex(FlexFunction f, Element child) : Node(unpack(std::move(child))), f_(f) {}
...
};
// 构造 flex 属性
Element flex(Element child) {
return std::make_shared<Flex>(function_flex, std::move(child));
}
Frame 帧
框架是一个可滚动的区域。其内部区域可能大于外部区域。滚动内部区域是为了使焦点元素可见。
// 在 element.h 声明
Element frame(Element);
// 在 frame.cpp 定义
class Frame : public Node {
public:
Frame(Elements children, bool x_frame, bool y_frame)
: Node(std::move(children)), x_frame_(x_frame), y_frame_(y_frame) {}
...
};
// 构造 frame 渲染节点
Element frame(Element child) {
return std::make_shared<Frame>(unpack(std::move(child)), true, true);
}
案例实践:自定义ListView部件,实现显示一个文本列表显示
#include <algorithm>
#include <array>
#include <chrono>
#include <iostream>
#include <random>
#include <string>
#include <vector>
#include "ftxui/dom/elements.hpp"
#include "ftxui/dom/node.hpp"
#include "ftxui/screen/string.hpp"
class ListView : public ftxui::Node {
public:
ListView(std::vector<std::string> data) : list(data){};
void ComputeRequirement() override {
int x = 0;
for (auto& item : list) {
x = std::max(x, ftxui::string_width(item));
}
requirement_.min_x = x;
requirement_.min_y = list.size();
}
void Render(ftxui::Screen& screen) override {
int x = box_.x_min;
int y = box_.y_min;
if (x > box_.x_max) {
return;
}
if (y > box_.y_max) {
return;
}
for (auto& item : list) {
for (const auto& cell : ftxui::Utf8ToGlyphs(item)) {
screen.PixelAt(x, y).character = cell;
++x;
if (x > box_.x_max) {
screen.PixelAt(x - 1, y).character = '~';
break;
}
}
x = 0;
y++;
if (y > box_.y_max) {
break;
}
}
}
private:
std::vector<std::string> list;
};
ftxui::Element list_view(std::vector<std::string> data) {
return std::make_shared<ListView>(data);
}
调用自定义组件,实现屏幕显示
int main(void) {
using namespace ftxui;
std::vector<std::string> data;
data.push_back("Whatever is worth doing is worth doing well.");
data.push_back("how are you");
data.push_back("A heart that loves is always young.");
auto document = list_view(data);
auto screen = Screen::Create(Dimension::Full());
Render(screen, document);
screen.Print();
return 0;
}