FTXUI 笔记(三)——dom树的节点渲染

本文解析了FTXUI库中的GUI布局原理,包括Node基类、Text部件、Bold装饰器及HBox布局管理器等核心组件的设计与实现。通过自定义ListView部件展示了如何构建文本列表。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

requirement.hpp

selection.hpp

Selection类

node.hpp

Node基类

dom树渲染流程(渲染Node树)

flexbox_config.hpp

FlexboxConfig类

linear_gradient.hpp

LinearGradient类

table.hpp

Table类

canvas.hpp

Canvas类

elements.hpp

Widget 部件

Decorator装饰器

Layout布局管理器

Flexibility属性

Frame 帧

案例实践:自定义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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值