diff --git a/komorebi-core/src/arrangement.rs b/komorebi-core/src/arrangement.rs index eb23a726..657267c2 100644 --- a/komorebi-core/src/arrangement.rs +++ b/komorebi-core/src/arrangement.rs @@ -256,15 +256,44 @@ impl Arrangement for CustomLayout { Option::from(1) }; + #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] + let primary_right = self.primary_width_percentage().map_or_else( + || area.right / self.len() as i32, + |percentage| (area.right / 100) * percentage as i32, + ); + for (idx, column) in self.iter().enumerate() { // If we are offsetting a tertiary column for which the threshold // has not yet been met, this loop should not run for that final // tertiary column if idx < self.len() - offset.unwrap_or(0) { - let column_area = self.column_area(area, idx, offset); + let column_area = if idx == 0 { + Self::column_area_with_last(self.len(), area, primary_right, None, offset) + } else { + Self::column_area_with_last( + self.len(), + area, + primary_right, + Option::from(dimensions[self.first_container_idx(idx - 1)]), + offset, + ) + }; match column { - Column::Primary | Column::Secondary(None) => { + Column::Primary(Option::Some(_)) => { + let main_column_area = if idx == 0 { + Self::main_column_area(area, primary_right, None) + } else { + Self::main_column_area( + area, + primary_right, + Option::from(dimensions[self.first_container_idx(idx - 1)]), + ) + }; + + dimensions.push(main_column_area); + } + Column::Primary(None) | Column::Secondary(None) => { dimensions.push(column_area); } Column::Secondary(Some(split)) => match split { @@ -278,6 +307,14 @@ impl Arrangement for CustomLayout { } }, Column::Tertiary(split) => { + let column_area = Self::column_area_with_last( + self.len(), + area, + primary_right, + Option::from(dimensions[self.first_container_idx(idx - 1)]), + offset, + ); + let remaining = container_count - tertiary_trigger_threshold; match split { diff --git a/komorebi-core/src/custom_layout.rs b/komorebi-core/src/custom_layout.rs index b8aa457a..79ad64d6 100644 --- a/komorebi-core/src/custom_layout.rs +++ b/komorebi-core/src/custom_layout.rs @@ -28,7 +28,7 @@ impl CustomLayout { #[must_use] pub fn primary_idx(&self) -> Option { for (i, column) in self.iter().enumerate() { - if let Column::Primary = column { + if let Column::Primary(_) = column { return Option::from(i); } } @@ -36,6 +36,18 @@ impl CustomLayout { None } + #[must_use] + pub fn primary_width_percentage(&self) -> Option { + for column in self.iter() { + if let Column::Primary(Option::Some(ColumnWidth::WidthPercentage(percentage))) = column + { + return Option::from(*percentage); + } + } + + None + } + #[must_use] pub fn is_valid(&self) -> bool { // A valid layout must have at least one column @@ -63,7 +75,7 @@ impl CustomLayout { for column in self.iter() { match column { - Column::Primary => primaries += 1, + Column::Primary(_) => primaries += 1, Column::Tertiary(_) => tertiaries += 1, Column::Secondary(_) => {} } @@ -78,7 +90,7 @@ impl CustomLayout { for (idx, column) in self.iter().enumerate() { match column { - Column::Primary | Column::Secondary(None) => { + Column::Primary(_) | Column::Secondary(None) => { count_map.insert(idx, 1); } Column::Secondary(Some(split)) => { @@ -156,16 +168,60 @@ impl CustomLayout { bottom: work_area.bottom, } } + + #[must_use] + pub fn column_area_with_last( + len: usize, + work_area: &Rect, + primary_right: i32, + last_column: Option, + offset: Option, + ) -> Rect { + let divisor = offset.map_or_else(|| len - 1, |offset| len - offset - 1); + + #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] + let equal_width = (work_area.right - primary_right) / divisor as i32; + let left = last_column.map_or(work_area.left, |last| last.left + last.right); + let right = equal_width; + + Rect { + left, + top: work_area.top, + right, + bottom: work_area.bottom, + } + } + + #[must_use] + pub fn main_column_area( + work_area: &Rect, + primary_right: i32, + last_column: Option, + ) -> Rect { + let left = last_column.map_or(work_area.left, |last| last.left + last.right); + + Rect { + left, + top: work_area.top, + right: primary_right, + bottom: work_area.bottom, + } + } } #[derive(Clone, Copy, Debug, Serialize, Deserialize)] #[serde(tag = "column", content = "configuration")] pub enum Column { - Primary, + Primary(Option), Secondary(Option), Tertiary(ColumnSplit), } +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub enum ColumnWidth { + WidthPercentage(usize), +} + #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub enum ColumnSplit { Horizontal, diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index b5c79e9a..ab1864d4 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -1040,9 +1040,10 @@ impl WindowManager { match workspace.layout() { Layout::Default(_) => {} Layout::Custom(layout) => { - let primary_idx = layout - .primary_idx() - .ok_or_else(|| anyhow!("this custom layout does not have a primary column"))?; + let primary_idx = + layout.first_container_idx(layout.primary_idx().ok_or_else(|| { + anyhow!("this custom layout does not have a primary column") + })?); if !workspace.containers().is_empty() && primary_idx < workspace.containers().len() { @@ -1067,9 +1068,10 @@ impl WindowManager { match workspace.layout() { Layout::Default(_) => { - let primary_idx = layout - .primary_idx() - .ok_or_else(|| anyhow!("this custom layout does not have a primary column"))?; + let primary_idx = + layout.first_container_idx(layout.primary_idx().ok_or_else(|| { + anyhow!("this custom layout does not have a primary column") + })?); if !workspace.containers().is_empty() && primary_idx < workspace.containers().len() { diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index b6a357ce..b841e3ce 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -352,15 +352,17 @@ impl Workspace { let primary_idx = match self.layout() { Layout::Default(_) => 0, - Layout::Custom(layout) => layout - .primary_idx() - .ok_or_else(|| anyhow!("this custom layout does not have a primary column"))?, + Layout::Custom(layout) => layout.first_container_idx( + layout + .primary_idx() + .ok_or_else(|| anyhow!("this custom layout does not have a primary column"))?, + ), }; self.containers_mut().insert(primary_idx, container); self.resize_dimensions_mut().insert(primary_idx, resize); - self.focus_container(0); + self.focus_container(primary_idx); Ok(()) }