Add support for layouts with parts of a fixed or unspecified size
This commit is contained in:
parent
7efc435bda
commit
624f842a20
3 changed files with 102 additions and 124 deletions
180
src/layout.rs
180
src/layout.rs
|
|
@ -5,58 +5,128 @@ use crate::panes::PositionAndSize;
|
||||||
|
|
||||||
fn split_space_to_parts_vertically(
|
fn split_space_to_parts_vertically(
|
||||||
space_to_split: &PositionAndSize,
|
space_to_split: &PositionAndSize,
|
||||||
percentages: Vec<u8>,
|
sizes: Vec<Option<SplitSize>>,
|
||||||
) -> Vec<PositionAndSize> {
|
) -> Vec<PositionAndSize> {
|
||||||
let mut split_parts = vec![];
|
let mut split_parts = Vec::new();
|
||||||
let mut current_x_position = space_to_split.x;
|
let mut current_x_position = space_to_split.x;
|
||||||
let width = space_to_split.columns - (percentages.len() - 1); // minus space for gaps
|
let mut current_width = 0;
|
||||||
for percentage in percentages.iter() {
|
let max_width = space_to_split.columns - (sizes.len() - 1); // minus space for gaps
|
||||||
let columns = (width as f32 * (*percentage as f32 / 100.0)) as usize; // TODO: round properly
|
|
||||||
|
let mut parts_to_grow = Vec::new();
|
||||||
|
|
||||||
|
// First fit in the parameterized sizes
|
||||||
|
for size in sizes {
|
||||||
|
let columns = match size {
|
||||||
|
Some(SplitSize::Percent(percent)) => {
|
||||||
|
(max_width as f32 * (percent as f32 / 100.0)) as usize
|
||||||
|
} // TODO: round properly
|
||||||
|
Some(SplitSize::Fixed(size)) => size as usize,
|
||||||
|
None => {
|
||||||
|
parts_to_grow.push(current_x_position);
|
||||||
|
1 // This is grown later on
|
||||||
|
}
|
||||||
|
};
|
||||||
split_parts.push(PositionAndSize {
|
split_parts.push(PositionAndSize {
|
||||||
x: current_x_position,
|
x: current_x_position,
|
||||||
y: space_to_split.y,
|
y: space_to_split.y,
|
||||||
columns,
|
columns,
|
||||||
rows: space_to_split.rows,
|
rows: space_to_split.rows,
|
||||||
});
|
});
|
||||||
|
current_width += columns;
|
||||||
current_x_position += columns + 1; // 1 for gap
|
current_x_position += columns + 1; // 1 for gap
|
||||||
}
|
}
|
||||||
let total_width = split_parts
|
|
||||||
.iter()
|
if current_width > max_width {
|
||||||
.fold(0, |total_width, part| total_width + part.columns);
|
panic!("Layout contained too many columns to fit onto the screen!");
|
||||||
if total_width < width {
|
}
|
||||||
// we have some extra space left, let's add it to the last part
|
|
||||||
let last_part_index = split_parts.len() - 1;
|
let mut last_flexible_index = split_parts.len() - 1;
|
||||||
let mut last_part = split_parts.get_mut(last_part_index).unwrap();
|
if let Some(new_columns) = (max_width - current_width).checked_div(parts_to_grow.len()) {
|
||||||
last_part.columns += width - total_width;
|
current_width = 0;
|
||||||
|
current_x_position = 0;
|
||||||
|
for (idx, part) in split_parts.iter_mut().enumerate() {
|
||||||
|
part.x = current_x_position;
|
||||||
|
if parts_to_grow.contains(&part.x) {
|
||||||
|
part.columns = new_columns;
|
||||||
|
last_flexible_index = idx;
|
||||||
|
}
|
||||||
|
current_width += part.columns;
|
||||||
|
current_x_position += part.columns + 1; // 1 for gap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if current_width < max_width {
|
||||||
|
// we have some extra space left, let's add it to the last flexible part
|
||||||
|
let extra = max_width - current_width;
|
||||||
|
let mut last_part = split_parts.get_mut(last_flexible_index).unwrap();
|
||||||
|
last_part.columns += extra;
|
||||||
|
for part in (&mut split_parts[last_flexible_index + 1..]).iter_mut() {
|
||||||
|
part.x += extra;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
split_parts
|
split_parts
|
||||||
}
|
}
|
||||||
|
|
||||||
fn split_space_to_parts_horizontally(
|
fn split_space_to_parts_horizontally(
|
||||||
space_to_split: &PositionAndSize,
|
space_to_split: &PositionAndSize,
|
||||||
percentages: Vec<u8>,
|
sizes: Vec<Option<SplitSize>>,
|
||||||
) -> Vec<PositionAndSize> {
|
) -> Vec<PositionAndSize> {
|
||||||
let mut split_parts = vec![];
|
let mut split_parts = Vec::new();
|
||||||
let mut current_y_position = space_to_split.y;
|
let mut current_y_position = space_to_split.y;
|
||||||
let height = space_to_split.rows - (percentages.len() - 1); // minus space for gaps
|
let mut current_height = 0;
|
||||||
for percentage in percentages.iter() {
|
let max_height = space_to_split.rows - (sizes.len() - 1); // minus space for gaps
|
||||||
let rows = (height as f32 * (*percentage as f32 / 100.0)) as usize; // TODO: round properly
|
|
||||||
|
let mut parts_to_grow = Vec::new();
|
||||||
|
|
||||||
|
for size in sizes {
|
||||||
|
let rows = match size {
|
||||||
|
Some(SplitSize::Percent(percent)) => {
|
||||||
|
(max_height as f32 * (percent as f32 / 100.0)) as usize
|
||||||
|
} // TODO: round properly
|
||||||
|
Some(SplitSize::Fixed(size)) => size as usize,
|
||||||
|
None => {
|
||||||
|
parts_to_grow.push(current_y_position);
|
||||||
|
1 // This is grown later on
|
||||||
|
}
|
||||||
|
};
|
||||||
split_parts.push(PositionAndSize {
|
split_parts.push(PositionAndSize {
|
||||||
x: space_to_split.x,
|
x: space_to_split.x,
|
||||||
y: current_y_position,
|
y: current_y_position,
|
||||||
columns: space_to_split.columns,
|
columns: space_to_split.columns,
|
||||||
rows,
|
rows,
|
||||||
});
|
});
|
||||||
|
current_height += rows;
|
||||||
current_y_position += rows + 1; // 1 for gap
|
current_y_position += rows + 1; // 1 for gap
|
||||||
}
|
}
|
||||||
let total_height = split_parts
|
|
||||||
.iter()
|
if current_height > max_height {
|
||||||
.fold(0, |total_height, part| total_height + part.rows);
|
panic!("Layout contained too many rows to fit onto the screen!");
|
||||||
if total_height < height {
|
}
|
||||||
// we have some extra space left, let's add it to the last part
|
|
||||||
let last_part_index = split_parts.len() - 1;
|
let mut last_flexible_index = split_parts.len() - 1;
|
||||||
let mut last_part = split_parts.get_mut(last_part_index).unwrap();
|
if let Some(new_rows) = (max_height - current_height).checked_div(parts_to_grow.len()) {
|
||||||
last_part.rows += height - total_height;
|
current_height = 0;
|
||||||
|
current_y_position = 0;
|
||||||
|
|
||||||
|
for (idx, part) in split_parts.iter_mut().enumerate() {
|
||||||
|
part.y = current_y_position;
|
||||||
|
if parts_to_grow.contains(&part.y) {
|
||||||
|
part.rows = new_rows;
|
||||||
|
last_flexible_index = idx;
|
||||||
|
}
|
||||||
|
current_height += part.rows;
|
||||||
|
current_y_position += part.rows + 1; // 1 for gap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if current_height < max_height {
|
||||||
|
// we have some extra space left, let's add it to the last flexible part
|
||||||
|
let extra = max_height - current_height;
|
||||||
|
let mut last_part = split_parts.get_mut(last_flexible_index).unwrap();
|
||||||
|
last_part.rows += extra;
|
||||||
|
for part in (&mut split_parts[last_flexible_index + 1..]).iter_mut() {
|
||||||
|
part.y += extra;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
split_parts
|
split_parts
|
||||||
}
|
}
|
||||||
|
|
@ -66,24 +136,11 @@ fn split_space(
|
||||||
layout: &Layout,
|
layout: &Layout,
|
||||||
) -> Vec<(Layout, PositionAndSize)> {
|
) -> Vec<(Layout, PositionAndSize)> {
|
||||||
let mut pane_positions = Vec::new();
|
let mut pane_positions = Vec::new();
|
||||||
let percentages: Vec<u8> = layout
|
let sizes: Vec<Option<SplitSize>> = layout.parts.iter().map(|part| part.split_size).collect();
|
||||||
.parts
|
|
||||||
.iter()
|
|
||||||
.map(|part| {
|
|
||||||
let split_size = part.split_size.as_ref();
|
|
||||||
match split_size {
|
|
||||||
Some(SplitSize::Percent(percent)) => *percent,
|
|
||||||
None => {
|
|
||||||
// TODO: if there is no split size, it should get the remaining "free space"
|
|
||||||
panic!("Please enter the percentage of the screen part");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let split_parts = match layout.direction {
|
let split_parts = match layout.direction {
|
||||||
Direction::Vertical => split_space_to_parts_vertically(space_to_split, percentages),
|
Direction::Vertical => split_space_to_parts_vertically(space_to_split, sizes),
|
||||||
Direction::Horizontal => split_space_to_parts_horizontally(space_to_split, percentages),
|
Direction::Horizontal => split_space_to_parts_horizontally(space_to_split, sizes),
|
||||||
};
|
};
|
||||||
for (i, part) in layout.parts.iter().enumerate() {
|
for (i, part) in layout.parts.iter().enumerate() {
|
||||||
let part_position_and_size = split_parts.get(i).unwrap();
|
let part_position_and_size = split_parts.get(i).unwrap();
|
||||||
|
|
@ -97,43 +154,16 @@ fn split_space(
|
||||||
pane_positions
|
pane_positions
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_layout_percentage_total(layout: &Layout) -> bool {
|
|
||||||
let total_percentages: u8 = layout
|
|
||||||
.parts
|
|
||||||
.iter()
|
|
||||||
.map(|part| {
|
|
||||||
let split_size = part.split_size.as_ref();
|
|
||||||
match split_size {
|
|
||||||
Some(SplitSize::Percent(percent)) => *percent,
|
|
||||||
None => {
|
|
||||||
// TODO: if there is no split size, it should get the remaining "free space"
|
|
||||||
panic!("Please enter the percentage of the screen part");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.sum();
|
|
||||||
if total_percentages != 100 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for part in layout.parts.iter() {
|
|
||||||
if !part.parts.is_empty() {
|
|
||||||
return validate_layout_percentage_total(part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
Horizontal,
|
Horizontal,
|
||||||
Vertical,
|
Vertical,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
||||||
pub enum SplitSize {
|
pub enum SplitSize {
|
||||||
Percent(u8), // 1 to 100
|
Percent(u8), // 1 to 100
|
||||||
|
Fixed(u16), // An absolute number of columns or rows
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
|
@ -158,17 +188,9 @@ impl Layout {
|
||||||
.unwrap_or_else(|_| panic!("could not read layout {}", &layout_path.display()));
|
.unwrap_or_else(|_| panic!("could not read layout {}", &layout_path.display()));
|
||||||
let layout: Layout = serde_yaml::from_str(&layout)
|
let layout: Layout = serde_yaml::from_str(&layout)
|
||||||
.unwrap_or_else(|_| panic!("could not parse layout {}", &layout_path.display()));
|
.unwrap_or_else(|_| panic!("could not parse layout {}", &layout_path.display()));
|
||||||
layout.validate();
|
|
||||||
|
|
||||||
layout
|
layout
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate(&self) {
|
|
||||||
if !validate_layout_percentage_total(&self) {
|
|
||||||
panic!("The total percent for each part should equal 100.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn total_terminal_panes(&self) -> usize {
|
pub fn total_terminal_panes(&self) -> usize {
|
||||||
let mut total_panes = 0;
|
let mut total_panes = 0;
|
||||||
total_panes += self.parts.len();
|
total_panes += self.parts.len();
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,6 @@ parts:
|
||||||
Percent: 20
|
Percent: 20
|
||||||
plugin: strider.wasm
|
plugin: strider.wasm
|
||||||
- direction: Horizontal
|
- direction: Horizontal
|
||||||
split_size:
|
|
||||||
Percent: 80
|
|
||||||
split_size:
|
|
||||||
Percent: 80
|
|
||||||
- direction: Vertical
|
- direction: Vertical
|
||||||
split_size:
|
split_size:
|
||||||
Percent: 20
|
Fixed: 1
|
||||||
|
|
@ -50,43 +50,3 @@ pub fn accepts_basic_layout() {
|
||||||
assert_snapshot!(next_to_last_snapshot);
|
assert_snapshot!(next_to_last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic(expected = "The total percent for each part should equal 100.")]
|
|
||||||
pub fn should_throw_for_more_than_100_percent_total() {
|
|
||||||
let fake_win_size = PositionAndSize {
|
|
||||||
columns: 121,
|
|
||||||
rows: 20,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
};
|
|
||||||
let mut fake_input_output = get_fake_os_input(&fake_win_size);
|
|
||||||
fake_input_output.add_terminal_input(&[&QUIT]);
|
|
||||||
|
|
||||||
let mut opts = CliArgs::default();
|
|
||||||
opts.layout = Some(PathBuf::from(
|
|
||||||
"src/tests/fixtures/layouts/parts-total-more-than-100-percent.yaml",
|
|
||||||
));
|
|
||||||
|
|
||||||
start(Box::new(fake_input_output.clone()), opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic(expected = "The total percent for each part should equal 100.")]
|
|
||||||
pub fn should_throw_for_less_than_100_percent_total() {
|
|
||||||
let fake_win_size = PositionAndSize {
|
|
||||||
columns: 121,
|
|
||||||
rows: 20,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
};
|
|
||||||
let mut fake_input_output = get_fake_os_input(&fake_win_size);
|
|
||||||
fake_input_output.add_terminal_input(&[&QUIT]);
|
|
||||||
|
|
||||||
let mut opts = CliArgs::default();
|
|
||||||
opts.layout = Some(PathBuf::from(
|
|
||||||
"src/tests/fixtures/layouts/parts-total-less-than-100-percent.yaml",
|
|
||||||
));
|
|
||||||
|
|
||||||
start(Box::new(fake_input_output.clone()), opts);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue