diff options
| -rw-r--r-- | niri-ipc/src/lib.rs | 62 | ||||
| -rw-r--r-- | src/layout/floating.rs | 37 | ||||
| -rw-r--r-- | src/layout/tests.rs | 2 | ||||
| -rw-r--r-- | src/layout/workspace.rs | 35 |
4 files changed, 113 insertions, 23 deletions
diff --git a/niri-ipc/src/lib.rs b/niri-ipc/src/lib.rs index 080db620..a7662b57 100644 --- a/niri-ipc/src/lib.rs +++ b/niri-ipc/src/lib.rs @@ -813,14 +813,14 @@ pub enum Action { /// How to change the X position. #[cfg_attr( feature = "clap", - arg(short, long, default_value = "+0", allow_negative_numbers = true) + arg(short, long, default_value = "+0", allow_hyphen_values = true) )] x: PositionChange, /// How to change the Y position. #[cfg_attr( feature = "clap", - arg(short, long, default_value = "+0", allow_negative_numbers = true) + arg(short, long, default_value = "+0", allow_hyphen_values = true) )] y: PositionChange, }, @@ -913,8 +913,12 @@ pub enum SizeChange { pub enum PositionChange { /// Set the position in logical pixels. SetFixed(f64), + /// Set the position as a proportion of the working area. + SetProportion(f64), /// Add or subtract to the current position in logical pixels. AdjustFixed(f64), + /// Add or subtract to the current position as a proportion of the working area. + AdjustProportion(f64), } /// Workspace reference (id, index or name) to operate on. @@ -1519,17 +1523,38 @@ impl FromStr for PositionChange { type Err = &'static str; fn from_str(s: &str) -> Result<Self, Self::Err> { - let value = s; - match value.bytes().next() { - Some(b'-' | b'+') => { - let value = value.parse().map_err(|_| "error parsing value")?; - Ok(Self::AdjustFixed(value)) + match s.split_once('%') { + Some((value, empty)) => { + if !empty.is_empty() { + return Err("trailing characters after '%' are not allowed"); + } + + match value.bytes().next() { + Some(b'-' | b'+') => { + let value = value.parse().map_err(|_| "error parsing value")?; + Ok(Self::AdjustProportion(value)) + } + Some(_) => { + let value = value.parse().map_err(|_| "error parsing value")?; + Ok(Self::SetProportion(value)) + } + None => Err("value is missing"), + } } - Some(_) => { - let value = value.parse().map_err(|_| "error parsing value")?; - Ok(Self::SetFixed(value)) + None => { + let value = s; + match value.bytes().next() { + Some(b'-' | b'+') => { + let value = value.parse().map_err(|_| "error parsing value")?; + Ok(Self::AdjustFixed(value)) + } + Some(_) => { + let value = value.parse().map_err(|_| "error parsing value")?; + Ok(Self::SetFixed(value)) + } + None => Err("value is missing"), + } } - None => Err("value is missing"), } } } @@ -1686,9 +1711,18 @@ mod tests { PositionChange::AdjustFixed(-10.), ); - assert!("10%".parse::<PositionChange>().is_err()); - assert!("+10%".parse::<PositionChange>().is_err()); - assert!("-10%".parse::<PositionChange>().is_err()); + assert_eq!( + "10%".parse::<PositionChange>().unwrap(), + PositionChange::SetProportion(10.) + ); + assert_eq!( + "+10%".parse::<PositionChange>().unwrap(), + PositionChange::AdjustProportion(10.) + ); + assert_eq!( + "-10%".parse::<PositionChange>().unwrap(), + PositionChange::AdjustProportion(-10.) + ); assert!("-".parse::<PositionChange>().is_err()); assert!("10% ".parse::<PositionChange>().is_err()); } diff --git a/src/layout/floating.rs b/src/layout/floating.rs index 5ef0a265..82b8c8cc 100644 --- a/src/layout/floating.rs +++ b/src/layout/floating.rs @@ -961,17 +961,42 @@ impl<W: LayoutElement> FloatingSpace<W> { }; let idx = self.idx_of(id).unwrap(); - let mut new_pos = self.data[idx].logical_pos; + let mut pos = self.data[idx].logical_pos; + + let available_width = self.working_area.size.w; + let available_height = self.working_area.size.h; + let working_area_loc = self.working_area.loc; + + const MAX_F: f64 = 10000.; + match x { - PositionChange::SetFixed(x) => new_pos.x = x + self.working_area.loc.x, - PositionChange::AdjustFixed(x) => new_pos.x += x, + PositionChange::SetFixed(x) => pos.x = x + working_area_loc.x, + PositionChange::SetProportion(prop) => { + let prop = (prop / 100.).clamp(0., MAX_F); + pos.x = available_width * prop + working_area_loc.x; + } + PositionChange::AdjustFixed(x) => pos.x += x, + PositionChange::AdjustProportion(prop) => { + let current_prop = (pos.x - working_area_loc.x) / available_width.max(1.); + let prop = (current_prop + prop / 100.).clamp(0., MAX_F); + pos.x = available_width * prop + working_area_loc.x; + } } match y { - PositionChange::SetFixed(y) => new_pos.y = y + self.working_area.loc.y, - PositionChange::AdjustFixed(y) => new_pos.y += y, + PositionChange::SetFixed(y) => pos.y = y + working_area_loc.y, + PositionChange::SetProportion(prop) => { + let prop = (prop / 100.).clamp(0., MAX_F); + pos.y = available_height * prop + working_area_loc.y; + } + PositionChange::AdjustFixed(y) => pos.y += y, + PositionChange::AdjustProportion(prop) => { + let current_prop = (pos.y - working_area_loc.y) / available_height.max(1.); + let prop = (current_prop + prop / 100.).clamp(0., MAX_F); + pos.y = available_height * prop + working_area_loc.y; + } } - self.move_to(idx, new_pos, animate); + self.move_to(idx, pos, animate); } pub fn center_window(&mut self, id: Option<&W::Id>) { diff --git a/src/layout/tests.rs b/src/layout/tests.rs index ced647b6..17a8a30e 100644 --- a/src/layout/tests.rs +++ b/src/layout/tests.rs @@ -311,7 +311,9 @@ fn arbitrary_size_change() -> impl Strategy<Value = SizeChange> { fn arbitrary_position_change() -> impl Strategy<Value = PositionChange> { prop_oneof![ (-1000f64..1000f64).prop_map(PositionChange::SetFixed), + any::<f64>().prop_map(PositionChange::SetProportion), (-1000f64..1000f64).prop_map(PositionChange::AdjustFixed), + any::<f64>().prop_map(PositionChange::AdjustProportion), any::<f64>().prop_map(PositionChange::SetFixed), any::<f64>().prop_map(PositionChange::AdjustFixed), ] diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index 27a593ae..ee57db6a 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -1511,14 +1511,18 @@ impl<W: LayoutElement> Workspace<W> { return; }; - let working_area_loc = self.floating.working_area().loc; let pos = self.floating.stored_or_default_tile_pos(tile); // If there's no stored floating position, we can only set both components at once, not // adjust. let pos = pos.or_else(|| { - (matches!(x, PositionChange::SetFixed(_)) - && matches!(y, PositionChange::SetFixed(_))) + (matches!( + x, + PositionChange::SetFixed(_) | PositionChange::SetProportion(_) + ) && matches!( + y, + PositionChange::SetFixed(_) | PositionChange::SetProportion(_) + )) .then_some(Point::default()) }); @@ -1526,13 +1530,38 @@ impl<W: LayoutElement> Workspace<W> { return; }; + let working_area = self.floating.working_area(); + let available_width = working_area.size.w; + let available_height = working_area.size.h; + let working_area_loc = working_area.loc; + + const MAX_F: f64 = 10000.; + match x { PositionChange::SetFixed(x) => pos.x = x + working_area_loc.x, + PositionChange::SetProportion(prop) => { + let prop = (prop / 100.).clamp(0., MAX_F); + pos.x = available_width * prop + working_area_loc.x; + } PositionChange::AdjustFixed(x) => pos.x += x, + PositionChange::AdjustProportion(prop) => { + let current_prop = (pos.x - working_area_loc.x) / available_width.max(1.); + let prop = (current_prop + prop / 100.).clamp(0., MAX_F); + pos.x = available_width * prop + working_area_loc.x; + } } match y { PositionChange::SetFixed(y) => pos.y = y + working_area_loc.y, + PositionChange::SetProportion(prop) => { + let prop = (prop / 100.).clamp(0., MAX_F); + pos.y = available_height * prop + working_area_loc.y; + } PositionChange::AdjustFixed(y) => pos.y += y, + PositionChange::AdjustProportion(prop) => { + let current_prop = (pos.y - working_area_loc.y) / available_height.max(1.); + let prop = (current_prop + prop / 100.).clamp(0., MAX_F); + pos.y = available_height * prop + working_area_loc.y; + } } let pos = self.floating.logical_to_size_frac(pos); |
