aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--niri-ipc/src/lib.rs62
-rw-r--r--src/layout/floating.rs37
-rw-r--r--src/layout/tests.rs2
-rw-r--r--src/layout/workspace.rs35
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);