feat(move-floating-window): percentage change (#2371)

* feat: add percentage change to move-floating-window

* fixes

---------

Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
This commit is contained in:
Mykyta Onipchenko
2025-10-18 01:39:50 -04:00
committed by GitHub
parent d31a90edb0
commit 79cdbc5748
4 changed files with 113 additions and 23 deletions
+48 -14
View File
@@ -813,14 +813,14 @@ pub enum Action {
/// How to change the X position. /// How to change the X position.
#[cfg_attr( #[cfg_attr(
feature = "clap", feature = "clap",
arg(short, long, default_value = "+0", allow_negative_numbers = true) arg(short, long, default_value = "+0", allow_hyphen_values = true)
)] )]
x: PositionChange, x: PositionChange,
/// How to change the Y position. /// How to change the Y position.
#[cfg_attr( #[cfg_attr(
feature = "clap", feature = "clap",
arg(short, long, default_value = "+0", allow_negative_numbers = true) arg(short, long, default_value = "+0", allow_hyphen_values = true)
)] )]
y: PositionChange, y: PositionChange,
}, },
@@ -913,8 +913,12 @@ pub enum SizeChange {
pub enum PositionChange { pub enum PositionChange {
/// Set the position in logical pixels. /// Set the position in logical pixels.
SetFixed(f64), SetFixed(f64),
/// Set the position as a proportion of the working area.
SetProportion(f64),
/// Add or subtract to the current position in logical pixels. /// Add or subtract to the current position in logical pixels.
AdjustFixed(f64), 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. /// Workspace reference (id, index or name) to operate on.
@@ -1519,17 +1523,38 @@ impl FromStr for PositionChange {
type Err = &'static str; type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let value = s; match s.split_once('%') {
match value.bytes().next() { Some((value, empty)) => {
Some(b'-' | b'+') => { if !empty.is_empty() {
let value = value.parse().map_err(|_| "error parsing value")?; return Err("trailing characters after '%' are not allowed");
Ok(Self::AdjustFixed(value)) }
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(_) => { None => {
let value = value.parse().map_err(|_| "error parsing value")?; let value = s;
Ok(Self::SetFixed(value)) 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.), PositionChange::AdjustFixed(-10.),
); );
assert!("10%".parse::<PositionChange>().is_err()); assert_eq!(
assert!("+10%".parse::<PositionChange>().is_err()); "10%".parse::<PositionChange>().unwrap(),
assert!("-10%".parse::<PositionChange>().is_err()); 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!("-".parse::<PositionChange>().is_err());
assert!("10% ".parse::<PositionChange>().is_err()); assert!("10% ".parse::<PositionChange>().is_err());
} }
+31 -6
View File
@@ -961,17 +961,42 @@ impl<W: LayoutElement> FloatingSpace<W> {
}; };
let idx = self.idx_of(id).unwrap(); 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 { match x {
PositionChange::SetFixed(x) => new_pos.x = x + self.working_area.loc.x, PositionChange::SetFixed(x) => pos.x = x + working_area_loc.x,
PositionChange::AdjustFixed(x) => new_pos.x += 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 { match y {
PositionChange::SetFixed(y) => new_pos.y = y + self.working_area.loc.y, PositionChange::SetFixed(y) => pos.y = y + working_area_loc.y,
PositionChange::AdjustFixed(y) => new_pos.y += 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>) { pub fn center_window(&mut self, id: Option<&W::Id>) {
+2
View File
@@ -311,7 +311,9 @@ fn arbitrary_size_change() -> impl Strategy<Value = SizeChange> {
fn arbitrary_position_change() -> impl Strategy<Value = PositionChange> { fn arbitrary_position_change() -> impl Strategy<Value = PositionChange> {
prop_oneof![ prop_oneof![
(-1000f64..1000f64).prop_map(PositionChange::SetFixed), (-1000f64..1000f64).prop_map(PositionChange::SetFixed),
any::<f64>().prop_map(PositionChange::SetProportion),
(-1000f64..1000f64).prop_map(PositionChange::AdjustFixed), (-1000f64..1000f64).prop_map(PositionChange::AdjustFixed),
any::<f64>().prop_map(PositionChange::AdjustProportion),
any::<f64>().prop_map(PositionChange::SetFixed), any::<f64>().prop_map(PositionChange::SetFixed),
any::<f64>().prop_map(PositionChange::AdjustFixed), any::<f64>().prop_map(PositionChange::AdjustFixed),
] ]
+32 -3
View File
@@ -1511,14 +1511,18 @@ impl<W: LayoutElement> Workspace<W> {
return; return;
}; };
let working_area_loc = self.floating.working_area().loc;
let pos = self.floating.stored_or_default_tile_pos(tile); 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 // If there's no stored floating position, we can only set both components at once, not
// adjust. // adjust.
let pos = pos.or_else(|| { let pos = pos.or_else(|| {
(matches!(x, PositionChange::SetFixed(_)) (matches!(
&& matches!(y, PositionChange::SetFixed(_))) x,
PositionChange::SetFixed(_) | PositionChange::SetProportion(_)
) && matches!(
y,
PositionChange::SetFixed(_) | PositionChange::SetProportion(_)
))
.then_some(Point::default()) .then_some(Point::default())
}); });
@@ -1526,13 +1530,38 @@ impl<W: LayoutElement> Workspace<W> {
return; 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 { match x {
PositionChange::SetFixed(x) => pos.x = x + working_area_loc.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::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 { match y {
PositionChange::SetFixed(y) => pos.y = y + working_area_loc.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::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); let pos = self.floating.logical_to_size_frac(pos);