概述

Ratatui中的坐标系从左到右,从上到下,原点 (0, 0) 位于终端的左上角。xy坐标由u16值表示,如下图所示。

image-20231223135022711

布局和小部件构成了Ratatui中UI的基础。布局决定了界面的结构,使用约束将屏幕划分为不同的部分,而小部件则用内容填充这些部分。

在将小部件呈现到屏幕上时,首先需要定义小部件将显示的区域。此区域在缓冲区中由具有特定高度和宽度的矩形表示。可以将此矩形指定为绝对位置和大小,也可以使用 Layout 结构体根据 LengthMinMaxRatioPercentage 等约束动态划分终端窗口。

The Layout struct

使用布局结构的一个简单示例:

use ratatui::prelude::*;

let layout = Layout::default()
    .direction(Direction::Vertical) //指定布局方向为垂直
    .constraints(vec![//约束集
        Constraint::Percentage(50),//添加一个约束,占比50%
        Constraint::Percentage(50),
    ])
    .split(frame.size());//使用split进行分割

在这个例子中,我们希望将可用空间垂直地分成两个相等的部分,每个部分分配50%的屏幕高度。 Layout::split 函数将终端窗口的总大小作为参数(由 Frame::size() 方法返回),然后根据指定的约束为每个矩形计算适当的大小和位置。

一旦定义了布局,就可以使用从此布局派生的矩形区域之一来呈现小部件。这可以通过调用 Frame::render_widgetframe::render_stateful_widget 方法来实现:

frame.render_widget(
    Paragraph::new("Top")
        .block(Block::new().borders(Borders::ALL)),
    layout[0]);
frame.render_widget(
    Paragraph::new("Bottom")
        .block(Block::new().borders(Borders::ALL)),
    layout[1]);

渲染结果如下

image-20231223135851538

嵌套布局

布局可以嵌套。这意味着您可以在外部布局的矩形内定义另一个布局。这种嵌套布局允许构建复杂而灵活的UI设计,同时仍然可以控制窗口小部件网格如何随终端窗口调整大小。来看一个示例

// 先定义一个外部布局
let outer_layout = Layout::default()
    .direction(Direction::Vertical)
    .constraints(vec![
        Constraint::Percentage(50),
        Constraint::Percentage(50),
    ])
    .split(f.size());

// 定义内部布局,并将其嵌入外部布局的某一区域
let inner_layout = Layout::default()
    .direction(Direction::Horizontal)
    .constraints(vec![
        Constraint::Percentage(25),
        Constraint::Percentage(75),
    ])
    .split(outer_layout[1]);//split接收一个Rect参数,即布局要嵌入的区域

下面在该嵌套布局中渲染组件

frame.render_widget(
    Paragraph::new("outer 0")
        .block(Block::new().borders(Borders::ALL)),
    outer_layout[0]);
frame.render_widget(
    Paragraph::new("inner 0")
        .block(Block::new().borders(Borders::ALL)),
    inner_layout[0]);
frame.render_widget(
    Paragraph::new("inner 1")
        .block(Block::new().borders(Borders::ALL)),
    inner_layout[1]);

渲染结果如下

image-20231223140426451

Constraints

Constraints规定了布局中组件的大小和排列。Ratatui框架提供了几种用于微调用户界面布局的约束类型:

  • Constraint::Length(u16) :此约束指定矩形应占用的特定行数或列数。这是由绝对大小决定的,不会随终端大小缩放而改变。
  • Constraint::Percentage(u16) :此约束提供相对于父布局或终端窗口本身大小的大小。例如, Constraint::Percentage(50) 表示一个矩形应该占据其父矩形大小的50%。
  • Constraint::Ratio(u16, u16) :利用占比可以更精细地分割布局。例如, Constraint::Ratio(1, 3) 将分配父级大小的1/3给此约束。
  • Constraint::Min(u16) :限制区域的最小值,不论区域如何缩小,也会保证该区域有最小宽度/高度。
  • Constraint::Max(u16) :限制区域的最打值,不论区域如何放大,也会保证该区域有最大宽度/高度。

Constraints可以在布局中混合和匹配,以创建动态和可调整的界面。在定义应用程序的布局时,可以使用这些约束:

let layout = Layout::default()
    .direction(Direction::Horizontal)//布局水平排列
    .constraints([
        Constraint::Length(10),//占10个字符宽度
        Constraint::Percentage(70),//占比70%
        Constraint::Min(5),//最小占5个字符宽度
    ]
    .into_iter())
    .split(frame.size());

在本例中,初始 Length 约束使第一个矩形的宽度为10个字符。下一个矩形将占总宽度的70%。最后的矩形将占用剩余的空间,但永远不会小于5个字符。

image-20231223141551572

边框会占据一个字符宽度,满足10字符宽度,70%占比,最小5字符宽度的约束,缩放至最小宽度

image-20231223141956948

也符合约束要求。