aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--resources/default-config.kdl11
-rw-r--r--src/config.rs98
-rw-r--r--src/input.rs3
-rw-r--r--src/layout.rs81
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 {