diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2023-10-03 11:38:42 +0400 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2023-10-03 11:38:42 +0400 |
| commit | 7558ac14e6473c98bc4eea442b28a5ae330115ef (patch) | |
| tree | ba94429719e506167204500e3adbd506da6fc043 | |
| parent | bb3fbe2e83e5173b7e0df40908280aec02dc1a4b (diff) | |
| download | niri-7558ac14e6473c98bc4eea442b28a5ae330115ef.tar.gz niri-7558ac14e6473c98bc4eea442b28a5ae330115ef.tar.bz2 niri-7558ac14e6473c98bc4eea442b28a5ae330115ef.zip | |
Add set-column-width action
| -rw-r--r-- | resources/default-config.kdl | 11 | ||||
| -rw-r--r-- | src/config.rs | 98 | ||||
| -rw-r--r-- | src/input.rs | 3 | ||||
| -rw-r--r-- | src/layout.rs | 81 |
4 files changed, 188 insertions, 5 deletions
diff --git a/resources/default-config.kdl b/resources/default-config.kdl index b68bd2a3..a6e3667e 100644 --- a/resources/default-config.kdl +++ b/resources/default-config.kdl @@ -173,6 +173,17 @@ binds { Mod+F { maximize-column; } Mod+Shift+F { fullscreen-window; } + // Finer width adjustments. + // This command can also: + // * set width in pixels: "1000" + // * adjust width in pixels: "-5" or "+5" + // * set width as a percentage of screen width: "25%" + // * adjust width as a percentage of screen width: "-10%" or "+10%" + // Pixel sizes use logical, or scaled, pixels. I.e. on an output with scale 2.0, + // set-column-width "100" will make the column occupy 200 physical screen pixels. + Mod+Minus { set-column-width "-10%"; } + Mod+Equal { set-column-width "+10%"; } + Print { screenshot; } Mod+Shift+E { quit; } diff --git a/src/config.rs b/src/config.rs index 7ff7e338..9f73518b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -181,10 +181,10 @@ impl Default for Cursor { } } -#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq)] +#[derive(knuffel::Decode, Debug, Default, PartialEq)] pub struct Binds(#[knuffel(children)] pub Vec<Bind>); -#[derive(knuffel::Decode, Debug, PartialEq, Eq)] +#[derive(knuffel::Decode, Debug, PartialEq)] pub struct Bind { #[knuffel(node_name)] pub key: Key, @@ -209,7 +209,7 @@ bitflags! { } } -#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)] +#[derive(knuffel::Decode, Debug, Clone, PartialEq)] pub enum Action { #[knuffel(skip)] None, @@ -248,6 +248,15 @@ pub enum Action { MoveWindowToMonitorUp, SwitchPresetColumnWidth, MaximizeColumn, + SetColumnWidth(#[knuffel(argument, str)] SizeChange), +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum SizeChange { + SetFixed(i32), + SetProportion(f64), + AdjustFixed(i32), + AdjustProportion(f64), } #[derive(knuffel::Decode, Debug, PartialEq)] @@ -383,6 +392,58 @@ impl FromStr for Key { } } +impl FromStr for SizeChange { + type Err = miette::Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s.split_once('%') { + Some((value, empty)) => { + if !empty.is_empty() { + return Err(miette!("trailing characters after '%' are not allowed")); + } + + match value.bytes().next() { + Some(b'-' | b'+') => { + let value = value + .parse() + .into_diagnostic() + .context("error parsing value")?; + Ok(Self::AdjustProportion(value)) + } + Some(_) => { + let value = value + .parse() + .into_diagnostic() + .context("error parsing value")?; + Ok(Self::SetProportion(value)) + } + None => Err(miette!("value is missing")), + } + } + None => { + let value = s; + match value.bytes().next() { + Some(b'-' | b'+') => { + let value = value + .parse() + .into_diagnostic() + .context("error parsing value")?; + Ok(Self::AdjustFixed(value)) + } + Some(_) => { + let value = value + .parse() + .into_diagnostic() + .context("error parsing value")?; + Ok(Self::SetFixed(value)) + } + None => Err(miette!("value is missing")), + } + } + } + } +} + #[cfg(test)] mod tests { use miette::NarratableReportHandler; @@ -586,4 +647,35 @@ mod tests { assert!("1920x1080@".parse::<Mode>().is_err()); assert!("1920x1080@60Hz".parse::<Mode>().is_err()); } + + #[test] + fn parse_size_change() { + assert_eq!( + "10".parse::<SizeChange>().unwrap(), + SizeChange::SetFixed(10), + ); + assert_eq!( + "+10".parse::<SizeChange>().unwrap(), + SizeChange::AdjustFixed(10), + ); + assert_eq!( + "-10".parse::<SizeChange>().unwrap(), + SizeChange::AdjustFixed(-10), + ); + assert_eq!( + "10%".parse::<SizeChange>().unwrap(), + SizeChange::SetProportion(10.), + ); + assert_eq!( + "+10%".parse::<SizeChange>().unwrap(), + SizeChange::AdjustProportion(10.), + ); + assert_eq!( + "-10%".parse::<SizeChange>().unwrap(), + SizeChange::AdjustProportion(-10.), + ); + + assert!("-".parse::<SizeChange>().is_err()); + assert!("10% ".parse::<SizeChange>().is_err()); + } } diff --git a/src/input.rs b/src/input.rs index b52d5c77..d40221b8 100644 --- a/src/input.rs +++ b/src/input.rs @@ -296,6 +296,9 @@ impl State { self.move_cursor_to_output(&output); } } + Action::SetColumnWidth(change) => { + self.niri.monitor_set.set_column_width(change); + } } } } diff --git a/src/layout.rs b/src/layout.rs index 89fc0a29..f0b58dd1 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -52,7 +52,7 @@ use smithay::wayland::compositor::with_states; use smithay::wayland::shell::xdg::SurfaceCachedState; use crate::animation::Animation; -use crate::config::{Color, Config}; +use crate::config::{Color, Config, SizeChange}; const PADDING: i32 = 16; const WIDTH_PROPORTIONS: [ColumnWidth; 3] = [ @@ -288,7 +288,8 @@ impl ColumnWidth { match self { ColumnWidth::Proportion(proportion) => (view_width as f64 * proportion).floor() as i32, ColumnWidth::PresetProportion(idx) => WIDTH_PROPORTIONS[idx].resolve(view_width), - ColumnWidth::Fixed(width) => width, + // FIXME: remove this PADDING from here after redesigning how padding works. + ColumnWidth::Fixed(width) => width + PADDING, } } } @@ -912,6 +913,13 @@ impl<W: LayoutElement> MonitorSet<W> { monitor.toggle_full_width(); } + pub fn set_column_width(&mut self, change: SizeChange) { + let Some(monitor) = self.active_monitor() else { + return; + }; + monitor.set_column_width(change); + } + pub fn focus_output(&mut self, output: &Output) { if let MonitorSet::Normal { monitors, @@ -1278,6 +1286,10 @@ impl<W: LayoutElement> Monitor<W> { fn toggle_full_width(&mut self) { self.active_workspace().toggle_full_width(); } + + fn set_column_width(&mut self, change: SizeChange) { + self.active_workspace().set_column_width(change); + } } impl Monitor<Window> { @@ -1871,6 +1883,18 @@ impl<W: LayoutElement> Workspace<W> { self.columns[self.active_column_idx].toggle_full_width(self.view_size, self.working_area); } + fn set_column_width(&mut self, change: SizeChange) { + if self.columns.is_empty() { + return; + } + + self.columns[self.active_column_idx].set_column_width( + self.view_size, + self.working_area, + change, + ); + } + pub fn set_fullscreen(&mut self, window: &W, is_fullscreen: bool) { let (mut col_idx, win_idx) = self .columns @@ -2201,6 +2225,48 @@ impl<W: LayoutElement> Column<W> { self.set_width(view_size, working_area, width); } + fn set_column_width( + &mut self, + view_size: Size<i32, Logical>, + working_area: Rectangle<i32, Logical>, + change: SizeChange, + ) { + let current_px = self.width.resolve(working_area.size.w - PADDING) - PADDING; + + let current = match self.width { + ColumnWidth::PresetProportion(idx) => WIDTH_PROPORTIONS[idx], + current => current, + }; + + // FIXME: fix overflows then remove limits. + const MAX_PX: i32 = 100000; + const MAX_F: f64 = 10000.; + + let width = match (current, change) { + (_, SizeChange::SetFixed(fixed)) => ColumnWidth::Fixed(fixed.clamp(1, MAX_PX)), + (_, SizeChange::SetProportion(proportion)) => { + ColumnWidth::Proportion((proportion / 100.).clamp(0., MAX_F)) + } + (_, SizeChange::AdjustFixed(delta)) => { + let width = current_px.saturating_add(delta).clamp(1, MAX_PX); + ColumnWidth::Fixed(width) + } + (ColumnWidth::Proportion(current), SizeChange::AdjustProportion(delta)) => { + let proportion = (current + delta / 100.).clamp(0., MAX_F); + ColumnWidth::Proportion(proportion) + } + (ColumnWidth::Fixed(_), SizeChange::AdjustProportion(delta)) => { + let current = + (current_px + PADDING) as f64 / (working_area.size.w - PADDING) as f64; + let proportion = (current + delta / 100.).clamp(0., MAX_F); + ColumnWidth::Proportion(proportion) + } + (ColumnWidth::PresetProportion(_), _) => unreachable!(), + }; + + self.set_width(view_size, working_area, width); + } + fn set_fullscreen( &mut self, view_size: Size<i32, Logical>, @@ -2384,6 +2450,15 @@ mod tests { }) } + fn arbitrary_size_change() -> impl Strategy<Value = SizeChange> { + prop_oneof![ + (0..).prop_map(SizeChange::SetFixed), + (0f64..).prop_map(SizeChange::SetProportion), + any::<i32>().prop_map(SizeChange::AdjustFixed), + any::<f64>().prop_map(SizeChange::AdjustProportion), + ] + } + #[derive(Debug, Clone, Copy, Arbitrary)] enum Op { AddOutput(#[proptest(strategy = "1..=5usize")] usize), @@ -2417,6 +2492,7 @@ mod tests { MoveWindowToOutput(#[proptest(strategy = "1..=5u8")] u8), SwitchPresetColumnWidth, MaximizeColumn, + SetColumnWidth(#[proptest(strategy = "arbitrary_size_change()")] SizeChange), Communicate(#[proptest(strategy = "1..=5usize")] usize), } @@ -2506,6 +2582,7 @@ mod tests { } Op::SwitchPresetColumnWidth => monitor_set.toggle_width(), Op::MaximizeColumn => monitor_set.toggle_full_width(), + Op::SetColumnWidth(change) => monitor_set.set_column_width(change), Op::Communicate(id) => { let mut window = None; match monitor_set { |
