mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-24 02:01:18 +07:00
Add set-column-width action
This commit is contained in:
@@ -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; }
|
||||
|
||||
|
||||
+95
-3
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,6 +296,9 @@ impl State {
|
||||
self.move_cursor_to_output(&output);
|
||||
}
|
||||
}
|
||||
Action::SetColumnWidth(change) => {
|
||||
self.niri.monitor_set.set_column_width(change);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+79
-2
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user