mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-23 02:05:33 +07:00
Add per-axis scroll speed config for input devices (#2109)
* Add per-axis scroll speed config for input devices. Accepts negative values to inverse scroll direction. Properly complements/overrides global `scroll-direction` setting. Includes docs and tests. * Update per-axis scroll factor implementation after testing - Refined configuration structure in niri-config - Updated input handling to use per-axis scroll factors - Added comprehensive test snapshots - Updated documentation with per-axis examples 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Simplify per-axis scroll factor implementation per review feedback - Make documentation concise and clear - Remove unnecessary comments and test helper functions - Use inline snapshots for tests - Rename get_factors() to h_v_factors() for clarity - Remove unnecessary .clone() calls (ScrollFactor is Copy) - Reduce test count to essential cases only - Fix comment about window factor override behavior 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Remove unnecessary ScrollFactor::new() helper function The maintainer prefers minimal code, so removing this helper and constructing ScrollFactor directly in tests. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix scroll factor behavior - all settings now multiply with window factor Per maintainer feedback, both combined and per-axis scroll settings should multiply with the window-specific scroll factor, not override it. This ensures consistent behavior regardless of configuration method. Also removed the now-unused has_per_axis_override() method. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Final cleanup: remove redundant comments and unused snapshot files - Removed unused snapshot files (now using inline snapshots) - Removed redundant inline comments in tests - Simplified test descriptions to be more concise 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Convert scroll factor parsing tests to use assert_debug_snapshot Updates parse_scroll_factor_combined, parse_scroll_factor_split, and parse_scroll_factor_partial tests to use assert_debug_snapshot instead of manual assert_eq comparisons, as requested in PR review. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Convert to inline snapshots as requested - Convert all scroll factor parsing tests to use inline snapshots instead of external files - Remove external snapshot files to keep test directory clean - All tests still pass with inline snapshot assertions 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix missed assert_eq in parse_scroll_factor_mixed test Converts the remaining assert_eq calls to assert_debug_snapshot with inline snapshots in the mixed syntax test function. Also fixes raw string delimiters from ### to #. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Convert scroll_factor_h_v_factors test to use assert_debug_snapshot Makes all scroll factor tests consistent by using snapshots instead of assert_eq for better maintainability. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fixes --------- Co-authored-by: Bernardo Kuri <github@bkuri.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
This commit is contained in:
+272
-10
@@ -191,6 +191,25 @@ pub enum TrackLayout {
|
||||
Window,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct ScrollFactor {
|
||||
#[knuffel(argument)]
|
||||
pub base: Option<FloatOrInt<0, 100>>,
|
||||
#[knuffel(property)]
|
||||
pub horizontal: Option<FloatOrInt<-100, 100>>,
|
||||
#[knuffel(property)]
|
||||
pub vertical: Option<FloatOrInt<-100, 100>>,
|
||||
}
|
||||
|
||||
impl ScrollFactor {
|
||||
pub fn h_v_factors(&self) -> (f64, f64) {
|
||||
let base_value = self.base.map(|f| f.0).unwrap_or(1.0);
|
||||
let h = self.horizontal.map(|f| f.0).unwrap_or(base_value);
|
||||
let v = self.vertical.map(|f| f.0).unwrap_or(base_value);
|
||||
(h, v)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
pub struct Touchpad {
|
||||
#[knuffel(child)]
|
||||
@@ -227,8 +246,8 @@ pub struct Touchpad {
|
||||
pub disabled_on_external_mouse: bool,
|
||||
#[knuffel(child)]
|
||||
pub middle_emulation: bool,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub scroll_factor: Option<FloatOrInt<0, 100>>,
|
||||
#[knuffel(child)]
|
||||
pub scroll_factor: Option<ScrollFactor>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
@@ -251,8 +270,8 @@ pub struct Mouse {
|
||||
pub left_handed: bool,
|
||||
#[knuffel(child)]
|
||||
pub middle_emulation: bool,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub scroll_factor: Option<FloatOrInt<0, 100>>,
|
||||
#[knuffel(child)]
|
||||
pub scroll_factor: Option<ScrollFactor>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
@@ -4054,6 +4073,237 @@ mod tests {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_scroll_factor_combined() {
|
||||
// Test combined scroll-factor syntax
|
||||
let parsed = do_parse(
|
||||
r#"
|
||||
input {
|
||||
mouse {
|
||||
scroll-factor 2.0
|
||||
}
|
||||
touchpad {
|
||||
scroll-factor 1.5
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_debug_snapshot!(parsed.input.mouse.scroll_factor, @r#"
|
||||
Some(
|
||||
ScrollFactor {
|
||||
base: Some(
|
||||
FloatOrInt(
|
||||
2.0,
|
||||
),
|
||||
),
|
||||
horizontal: None,
|
||||
vertical: None,
|
||||
},
|
||||
)
|
||||
"#);
|
||||
assert_debug_snapshot!(parsed.input.touchpad.scroll_factor, @r#"
|
||||
Some(
|
||||
ScrollFactor {
|
||||
base: Some(
|
||||
FloatOrInt(
|
||||
1.5,
|
||||
),
|
||||
),
|
||||
horizontal: None,
|
||||
vertical: None,
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_scroll_factor_split() {
|
||||
// Test split horizontal/vertical syntax
|
||||
let parsed = do_parse(
|
||||
r#"
|
||||
input {
|
||||
mouse {
|
||||
scroll-factor horizontal=2.0 vertical=-1.0
|
||||
}
|
||||
touchpad {
|
||||
scroll-factor horizontal=-1.5 vertical=0.5
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_debug_snapshot!(parsed.input.mouse.scroll_factor, @r#"
|
||||
Some(
|
||||
ScrollFactor {
|
||||
base: None,
|
||||
horizontal: Some(
|
||||
FloatOrInt(
|
||||
2.0,
|
||||
),
|
||||
),
|
||||
vertical: Some(
|
||||
FloatOrInt(
|
||||
-1.0,
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
"#);
|
||||
assert_debug_snapshot!(parsed.input.touchpad.scroll_factor, @r#"
|
||||
Some(
|
||||
ScrollFactor {
|
||||
base: None,
|
||||
horizontal: Some(
|
||||
FloatOrInt(
|
||||
-1.5,
|
||||
),
|
||||
),
|
||||
vertical: Some(
|
||||
FloatOrInt(
|
||||
0.5,
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_scroll_factor_partial() {
|
||||
// Test partial specification (only one axis)
|
||||
let parsed = do_parse(
|
||||
r#"
|
||||
input {
|
||||
mouse {
|
||||
scroll-factor horizontal=2.0
|
||||
}
|
||||
touchpad {
|
||||
scroll-factor vertical=-1.5
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_debug_snapshot!(parsed.input.mouse.scroll_factor, @r#"
|
||||
Some(
|
||||
ScrollFactor {
|
||||
base: None,
|
||||
horizontal: Some(
|
||||
FloatOrInt(
|
||||
2.0,
|
||||
),
|
||||
),
|
||||
vertical: None,
|
||||
},
|
||||
)
|
||||
"#);
|
||||
assert_debug_snapshot!(parsed.input.touchpad.scroll_factor, @r#"
|
||||
Some(
|
||||
ScrollFactor {
|
||||
base: None,
|
||||
horizontal: None,
|
||||
vertical: Some(
|
||||
FloatOrInt(
|
||||
-1.5,
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_scroll_factor_mixed() {
|
||||
// Test mixed base + override syntax
|
||||
let parsed = do_parse(
|
||||
r#"
|
||||
input {
|
||||
mouse {
|
||||
scroll-factor 2 vertical=-1
|
||||
}
|
||||
touchpad {
|
||||
scroll-factor 1.5 horizontal=3
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_debug_snapshot!(parsed.input.mouse.scroll_factor, @r#"
|
||||
Some(
|
||||
ScrollFactor {
|
||||
base: Some(
|
||||
FloatOrInt(
|
||||
2.0,
|
||||
),
|
||||
),
|
||||
horizontal: None,
|
||||
vertical: Some(
|
||||
FloatOrInt(
|
||||
-1.0,
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
"#);
|
||||
assert_debug_snapshot!(parsed.input.touchpad.scroll_factor, @r#"
|
||||
Some(
|
||||
ScrollFactor {
|
||||
base: Some(
|
||||
FloatOrInt(
|
||||
1.5,
|
||||
),
|
||||
),
|
||||
horizontal: Some(
|
||||
FloatOrInt(
|
||||
3.0,
|
||||
),
|
||||
),
|
||||
vertical: None,
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scroll_factor_h_v_factors() {
|
||||
let sf = ScrollFactor {
|
||||
base: Some(FloatOrInt(2.0)),
|
||||
horizontal: None,
|
||||
vertical: None,
|
||||
};
|
||||
assert_debug_snapshot!(sf.h_v_factors(), @r#"
|
||||
(
|
||||
2.0,
|
||||
2.0,
|
||||
)
|
||||
"#);
|
||||
|
||||
let sf = ScrollFactor {
|
||||
base: None,
|
||||
horizontal: Some(FloatOrInt(3.0)),
|
||||
vertical: Some(FloatOrInt(-1.0)),
|
||||
};
|
||||
assert_debug_snapshot!(sf.h_v_factors(), @r#"
|
||||
(
|
||||
3.0,
|
||||
-1.0,
|
||||
)
|
||||
"#);
|
||||
|
||||
let sf = ScrollFactor {
|
||||
base: Some(FloatOrInt(2.0)),
|
||||
horizontal: Some(FloatOrInt(1.0)),
|
||||
vertical: None,
|
||||
};
|
||||
assert_debug_snapshot!(sf.h_v_factors(), @r"
|
||||
(
|
||||
1.0,
|
||||
2.0,
|
||||
)
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
let parsed = do_parse(
|
||||
@@ -4370,9 +4620,15 @@ mod tests {
|
||||
disabled_on_external_mouse: true,
|
||||
middle_emulation: false,
|
||||
scroll_factor: Some(
|
||||
FloatOrInt(
|
||||
0.9,
|
||||
),
|
||||
ScrollFactor {
|
||||
base: Some(
|
||||
FloatOrInt(
|
||||
0.9,
|
||||
),
|
||||
),
|
||||
horizontal: None,
|
||||
vertical: None,
|
||||
},
|
||||
),
|
||||
},
|
||||
mouse: Mouse {
|
||||
@@ -4394,9 +4650,15 @@ mod tests {
|
||||
left_handed: false,
|
||||
middle_emulation: true,
|
||||
scroll_factor: Some(
|
||||
FloatOrInt(
|
||||
0.2,
|
||||
),
|
||||
ScrollFactor {
|
||||
base: Some(
|
||||
FloatOrInt(
|
||||
0.2,
|
||||
),
|
||||
),
|
||||
horizontal: None,
|
||||
vertical: None,
|
||||
},
|
||||
),
|
||||
},
|
||||
trackpoint: Trackpoint {
|
||||
|
||||
Reference in New Issue
Block a user