From 1dae45c58d7eabeda21ef490d712915890bf6cff Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Mon, 17 Jun 2024 09:16:28 +0300 Subject: Refactor layout to fractional-logical Lets borders, gaps, and everything else stay pixel-perfect even with fractional scale. Allows setting fractional border widths, gaps, struts. See the new wiki .md for more details. --- niri-config/src/lib.rs | 114 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 92 insertions(+), 22 deletions(-) (limited to 'niri-config/src') diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index 4fbb96e1..a9285fad 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -331,6 +331,10 @@ pub struct Position { pub y: i32, } +// MIN and MAX generics are only used during parsing to check the value. +#[derive(Debug, Default, Clone, Copy, PartialEq)] +pub struct FloatOrInt(pub f64); + #[derive(knuffel::Decode, Debug, Clone, PartialEq)] pub struct Layout { #[knuffel(child, default)] @@ -344,7 +348,7 @@ pub struct Layout { #[knuffel(child, unwrap(argument), default)] pub center_focused_column: CenterFocusedColumn, #[knuffel(child, unwrap(argument), default = Self::default().gaps)] - pub gaps: u16, + pub gaps: FloatOrInt<0, 65535>, #[knuffel(child, default)] pub struts: Struts, } @@ -357,7 +361,7 @@ impl Default for Layout { preset_column_widths: Default::default(), default_column_width: Default::default(), center_focused_column: Default::default(), - gaps: 16, + gaps: FloatOrInt(16.), struts: Default::default(), } } @@ -374,7 +378,7 @@ pub struct FocusRing { #[knuffel(child)] pub off: bool, #[knuffel(child, unwrap(argument), default = Self::default().width)] - pub width: u16, + pub width: FloatOrInt<0, 65535>, #[knuffel(child, default = Self::default().active_color)] pub active_color: Color, #[knuffel(child, default = Self::default().inactive_color)] @@ -389,7 +393,7 @@ impl Default for FocusRing { fn default() -> Self { Self { off: false, - width: 4, + width: FloatOrInt(4.), active_color: Color::new(127, 200, 255, 255), inactive_color: Color::new(80, 80, 80, 255), active_gradient: None, @@ -422,7 +426,7 @@ pub struct Border { #[knuffel(child)] pub off: bool, #[knuffel(child, unwrap(argument), default = Self::default().width)] - pub width: u16, + pub width: FloatOrInt<0, 65535>, #[knuffel(child, default = Self::default().active_color)] pub active_color: Color, #[knuffel(child, default = Self::default().inactive_color)] @@ -437,7 +441,7 @@ impl Default for Border { fn default() -> Self { Self { off: true, - width: 4, + width: FloatOrInt(4.), active_color: Color::new(255, 200, 127, 255), inactive_color: Color::new(80, 80, 80, 255), active_gradient: None, @@ -519,16 +523,16 @@ pub enum PresetWidth { #[derive(Debug, Clone, PartialEq)] pub struct DefaultColumnWidth(pub Option); -#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)] +#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)] pub struct Struts { #[knuffel(child, unwrap(argument), default)] - pub left: u16, + pub left: FloatOrInt<0, 65535>, #[knuffel(child, unwrap(argument), default)] - pub right: u16, + pub right: FloatOrInt<0, 65535>, #[knuffel(child, unwrap(argument), default)] - pub top: u16, + pub top: FloatOrInt<0, 65535>, #[knuffel(child, unwrap(argument), default)] - pub bottom: u16, + pub bottom: FloatOrInt<0, 65535>, } #[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)] @@ -863,7 +867,7 @@ pub struct BorderRule { #[knuffel(child)] pub on: bool, #[knuffel(child, unwrap(argument))] - pub width: Option, + pub width: Option>, #[knuffel(child)] pub active_color: Option, #[knuffel(child)] @@ -1144,6 +1148,72 @@ impl knuffel::DecodeScalar for WorkspaceRefere } } +impl knuffel::DecodeScalar + for FloatOrInt +{ + fn type_check( + type_name: &Option>, + ctx: &mut knuffel::decode::Context, + ) { + if let Some(type_name) = &type_name { + ctx.emit_error(DecodeError::unexpected( + type_name, + "type name", + "no type name expected for this node", + )); + } + } + + fn raw_decode( + val: &knuffel::span::Spanned, + ctx: &mut knuffel::decode::Context, + ) -> Result> { + match &**val { + knuffel::ast::Literal::Int(ref value) => match value.try_into() { + Ok(v) => { + if (MIN..=MAX).contains(&v) { + Ok(FloatOrInt(f64::from(v))) + } else { + ctx.emit_error(DecodeError::conversion( + val, + format!("value must be between {MIN} and {MAX}"), + )); + Ok(FloatOrInt::default()) + } + } + Err(e) => { + ctx.emit_error(DecodeError::conversion(val, e)); + Ok(FloatOrInt::default()) + } + }, + knuffel::ast::Literal::Decimal(ref value) => match value.try_into() { + Ok(v) => { + if (f64::from(MIN)..=f64::from(MAX)).contains(&v) { + Ok(FloatOrInt(v)) + } else { + ctx.emit_error(DecodeError::conversion( + val, + format!("value must be between {MIN} and {MAX}"), + )); + Ok(FloatOrInt::default()) + } + } + Err(e) => { + ctx.emit_error(DecodeError::conversion(val, e)); + Ok(FloatOrInt::default()) + } + }, + _ => { + ctx.emit_error(DecodeError::unsupported( + val, + "Unsupported value, only numbers are recognized", + )); + Ok(FloatOrInt::default()) + } + } + } +} + #[derive(knuffel::Decode, Debug, Default, PartialEq)] pub struct DebugConfig { #[knuffel(child, unwrap(argument))] @@ -2483,7 +2553,7 @@ mod tests { border { on - width 8 + width 8.5 } } @@ -2579,7 +2649,7 @@ mod tests { layout: Layout { focus_ring: FocusRing { off: false, - width: 5, + width: FloatOrInt(5.), active_color: Color { r: 0, g: 100, @@ -2602,7 +2672,7 @@ mod tests { }, border: Border { off: false, - width: 3, + width: FloatOrInt(3.), active_color: Color { r: 255, g: 200, @@ -2627,12 +2697,12 @@ mod tests { default_column_width: Some(DefaultColumnWidth(Some(PresetWidth::Proportion( 0.25, )))), - gaps: 8, + gaps: FloatOrInt(8.), struts: Struts { - left: 1, - right: 2, - top: 3, - bottom: 0, + left: FloatOrInt(1.), + right: FloatOrInt(2.), + top: FloatOrInt(3.), + bottom: FloatOrInt(0.), }, center_focused_column: CenterFocusedColumn::OnOverflow, }, @@ -2716,12 +2786,12 @@ mod tests { open_fullscreen: Some(false), focus_ring: BorderRule { off: true, - width: Some(3), + width: Some(FloatOrInt(3.)), ..Default::default() }, border: BorderRule { on: true, - width: Some(8), + width: Some(FloatOrInt(8.5)), ..Default::default() }, ..Default::default() -- cgit