diff --git a/src/layout.rs b/src/layout.rs index c82fc404..193de93a 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -5,58 +5,128 @@ use crate::panes::PositionAndSize; fn split_space_to_parts_vertically( space_to_split: &PositionAndSize, - percentages: Vec, + sizes: Vec>, ) -> Vec { - let mut split_parts = vec![]; + let mut split_parts = Vec::new(); let mut current_x_position = space_to_split.x; - let width = space_to_split.columns - (percentages.len() - 1); // minus space for gaps - for percentage in percentages.iter() { - let columns = (width as f32 * (*percentage as f32 / 100.0)) as usize; // TODO: round properly + let mut current_width = 0; + let max_width = space_to_split.columns - (sizes.len() - 1); // minus space for gaps + + 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 { x: current_x_position, y: space_to_split.y, columns, rows: space_to_split.rows, }); + current_width += columns; current_x_position += columns + 1; // 1 for gap } - let total_width = split_parts - .iter() - .fold(0, |total_width, part| total_width + part.columns); - 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_part = split_parts.get_mut(last_part_index).unwrap(); - last_part.columns += width - total_width; + + if current_width > max_width { + panic!("Layout contained too many columns to fit onto the screen!"); + } + + let mut last_flexible_index = split_parts.len() - 1; + if let Some(new_columns) = (max_width - current_width).checked_div(parts_to_grow.len()) { + 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 } fn split_space_to_parts_horizontally( space_to_split: &PositionAndSize, - percentages: Vec, + sizes: Vec>, ) -> Vec { - let mut split_parts = vec![]; + let mut split_parts = Vec::new(); let mut current_y_position = space_to_split.y; - let height = space_to_split.rows - (percentages.len() - 1); // minus space for gaps - for percentage in percentages.iter() { - let rows = (height as f32 * (*percentage as f32 / 100.0)) as usize; // TODO: round properly + let mut current_height = 0; + let max_height = space_to_split.rows - (sizes.len() - 1); // minus space for gaps + + 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 { x: space_to_split.x, y: current_y_position, columns: space_to_split.columns, rows, }); + current_height += rows; current_y_position += rows + 1; // 1 for gap } - let total_height = split_parts - .iter() - .fold(0, |total_height, part| total_height + part.rows); - 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_part = split_parts.get_mut(last_part_index).unwrap(); - last_part.rows += height - total_height; + + if current_height > max_height { + panic!("Layout contained too many rows to fit onto the screen!"); + } + + let mut last_flexible_index = split_parts.len() - 1; + if let Some(new_rows) = (max_height - current_height).checked_div(parts_to_grow.len()) { + 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 } @@ -66,24 +136,11 @@ fn split_space( layout: &Layout, ) -> Vec<(Layout, PositionAndSize)> { let mut pane_positions = Vec::new(); - let percentages: Vec = 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"); - } - } - }) - .collect(); + let sizes: Vec> = layout.parts.iter().map(|part| part.split_size).collect(); let split_parts = match layout.direction { - Direction::Vertical => split_space_to_parts_vertically(space_to_split, percentages), - Direction::Horizontal => split_space_to_parts_horizontally(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, sizes), }; for (i, part) in layout.parts.iter().enumerate() { let part_position_and_size = split_parts.get(i).unwrap(); @@ -97,43 +154,16 @@ fn split_space( 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)] pub enum Direction { Horizontal, Vertical, } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] pub enum SplitSize { Percent(u8), // 1 to 100 + Fixed(u16), // An absolute number of columns or rows } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -158,17 +188,9 @@ impl Layout { .unwrap_or_else(|_| panic!("could not read layout {}", &layout_path.display())); let layout: Layout = serde_yaml::from_str(&layout) .unwrap_or_else(|_| panic!("could not parse layout {}", &layout_path.display())); - layout.validate(); - 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 { let mut total_panes = 0; total_panes += self.parts.len(); diff --git a/src/tests/fixtures/layouts/panes-with-plugins.yaml b/src/tests/fixtures/layouts/panes-with-plugins.yaml index 7fb13b2e..9bb35e08 100644 --- a/src/tests/fixtures/layouts/panes-with-plugins.yaml +++ b/src/tests/fixtures/layouts/panes-with-plugins.yaml @@ -8,10 +8,6 @@ parts: Percent: 20 plugin: strider.wasm - direction: Horizontal - split_size: - Percent: 80 - split_size: - Percent: 80 - direction: Vertical split_size: - Percent: 20 \ No newline at end of file + Fixed: 1 \ No newline at end of file diff --git a/src/tests/integration/layouts.rs b/src/tests/integration/layouts.rs index 35ff7188..2086ac72 100644 --- a/src/tests/integration/layouts.rs +++ b/src/tests/integration/layouts.rs @@ -50,43 +50,3 @@ pub fn accepts_basic_layout() { assert_snapshot!(next_to_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); -}