Skip to main content

itools_tui/
layout.rs

1//! 布局系统模块
2//!
3//! 提供布局相关功能,包括垂直布局、水平布局和网格布局。
4
5/// 布局约束
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum Constraint {
8    /// 固定大小
9    Length(u16),
10    /// 剩余空间的比例
11    Ratio(u32, u32),
12    /// 尽可能小
13    Min(u16),
14    /// 尽可能大
15    Max(u16),
16    /// 填充剩余空间
17    Fill,
18}
19
20/// 布局方向
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Direction {
23    /// 垂直方向
24    Vertical,
25    /// 水平方向
26    Horizontal,
27}
28
29/// 布局
30pub struct Layout {
31    direction: Direction,
32    constraints: Vec<Constraint>,
33}
34
35impl Layout {
36    /// 创建垂直布局
37    pub fn vertical(constraints: Vec<Constraint>) -> Self {
38        Self { direction: Direction::Vertical, constraints }
39    }
40
41    /// 创建水平布局
42    pub fn horizontal(constraints: Vec<Constraint>) -> Self {
43        Self { direction: Direction::Horizontal, constraints }
44    }
45
46    /// 分割区域
47    pub fn split(&self, area: Rect) -> Vec<Rect> {
48        match self.direction {
49            Direction::Vertical => self.split_vertical(area),
50            Direction::Horizontal => self.split_horizontal(area),
51        }
52    }
53
54    /// 垂直分割
55    fn split_vertical(&self, area: Rect) -> Vec<Rect> {
56        let mut result = Vec::new();
57        let mut remaining_height = area.height;
58        let mut y = area.y;
59
60        // 计算固定长度和最小长度
61        let mut fixed_length = 0;
62        let mut min_length = 0;
63
64        for constraint in &self.constraints {
65            match constraint {
66                Constraint::Length(len) => fixed_length += len,
67                Constraint::Min(len) => min_length += len,
68                _ => {}
69            }
70        }
71
72        // 计算可分配的空间
73        let available_space = remaining_height.saturating_sub(fixed_length);
74
75        // 计算比例总和
76        let mut ratio_sum = 0;
77        for constraint in &self.constraints {
78            if let Constraint::Ratio(numerator, denominator) = constraint {
79                ratio_sum += numerator * 1000 / denominator;
80            }
81        }
82
83        // 分割空间
84        for constraint in &self.constraints {
85            let height = match constraint {
86                Constraint::Length(len) => *len,
87                Constraint::Min(len) => *len,
88                Constraint::Max(len) => std::cmp::min(*len, remaining_height),
89                Constraint::Ratio(numerator, denominator) => {
90                    if ratio_sum > 0 {
91                        (available_space as u32 * numerator * 1000 / denominator / ratio_sum) as u16
92                    }
93                    else {
94                        0
95                    }
96                }
97                Constraint::Fill => remaining_height,
98            };
99
100            if height > 0 {
101                result.push(Rect { x: area.x, y, width: area.width, height });
102                y += height;
103                remaining_height -= height;
104            }
105        }
106
107        result
108    }
109
110    /// 水平分割
111    fn split_horizontal(&self, area: Rect) -> Vec<Rect> {
112        let mut result = Vec::new();
113        let mut remaining_width = area.width;
114        let mut x = area.x;
115
116        // 计算固定长度和最小长度
117        let mut fixed_length = 0;
118        let mut min_length = 0;
119
120        for constraint in &self.constraints {
121            match constraint {
122                Constraint::Length(len) => fixed_length += len,
123                Constraint::Min(len) => min_length += len,
124                _ => {}
125            }
126        }
127
128        // 计算可分配的空间
129        let available_space = remaining_width.saturating_sub(fixed_length);
130
131        // 计算比例总和
132        let mut ratio_sum = 0;
133        for constraint in &self.constraints {
134            if let Constraint::Ratio(numerator, denominator) = constraint {
135                ratio_sum += numerator * 1000 / denominator;
136            }
137        }
138
139        // 分割空间
140        for constraint in &self.constraints {
141            let width = match constraint {
142                Constraint::Length(len) => *len,
143                Constraint::Min(len) => *len,
144                Constraint::Max(len) => std::cmp::min(*len, remaining_width),
145                Constraint::Ratio(numerator, denominator) => {
146                    if ratio_sum > 0 {
147                        (available_space as u32 * numerator * 1000 / denominator / ratio_sum) as u16
148                    }
149                    else {
150                        0
151                    }
152                }
153                Constraint::Fill => remaining_width,
154            };
155
156            if width > 0 {
157                result.push(Rect { x, y: area.y, width, height: area.height });
158                x += width;
159                remaining_width -= width;
160            }
161        }
162
163        result
164    }
165}
166
167/// 矩形区域
168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub struct Rect {
170    /// x 坐标
171    pub x: u16,
172    /// y 坐标
173    pub y: u16,
174    /// 宽度
175    pub width: u16,
176    /// 高度
177    pub height: u16,
178}
179
180impl Rect {
181    /// 创建新的矩形
182    pub fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
183        Self { x, y, width, height }
184    }
185
186    /// 从大小创建矩形
187    pub fn from_size(width: u16, height: u16) -> Self {
188        Self { x: 0, y: 0, width, height }
189    }
190
191    /// 检查点是否在矩形内
192    pub fn contains(&self, x: u16, y: u16) -> bool {
193        x >= self.x && x < self.x + self.width && y >= self.y && y < self.y + self.height
194    }
195
196    /// 缩小矩形
197    pub fn shrink(&self, margin: u16) -> Self {
198        Self {
199            x: self.x + margin,
200            y: self.y + margin,
201            width: self.width.saturating_sub(2 * margin),
202            height: self.height.saturating_sub(2 * margin),
203        }
204    }
205}