mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-22 20:53:19 +00:00
feat(layout): add Rect::clamp()
method (#749)
* feat(layout): add a Rect::clamp() method This ensures a rectangle does not end up outside an area. This is useful when you want to be able to dynamically move a rectangle around, but keep it constrained to a certain area. For example, this can be used to implement a draggable window that can be moved around, but not outside the terminal window. ```rust let window_area = Rect::new(state.x, state.y, 20, 20).clamp(area); state.x = rect.x; state.y = rect.y; ``` * refactor: use rstest to simplify clamp test * fix: use rstest description instead of string test layout::rect::tests:🗜️:case_01_inside ... ok test layout::rect::tests:🗜️:case_02_up_left ... ok test layout::rect::tests:🗜️:case_04_up_right ... ok test layout::rect::tests:🗜️:case_05_left ... ok test layout::rect::tests:🗜️:case_03_up ... ok test layout::rect::tests:🗜️:case_06_right ... ok test layout::rect::tests:🗜️:case_07_down_left ... ok test layout::rect::tests:🗜️:case_08_down ... ok test layout::rect::tests:🗜️:case_09_down_right ... ok test layout::rect::tests:🗜️:case_10_too_wide ... ok test layout::rect::tests:🗜️:case_11_too_tall ... ok test layout::rect::tests:🗜️:case_12_too_large ... ok * fix: less ambiguous docs for this / other rect * fix: move rstest to dev deps
This commit is contained in:
parent
fe84141119
commit
f13fd73d9e
2 changed files with 53 additions and 0 deletions
|
@ -54,6 +54,7 @@ fakeit = "1.1"
|
|||
palette = "0.7.3"
|
||||
pretty_assertions = "1.4.0"
|
||||
rand = "0.8.5"
|
||||
rstest = "0.18.2"
|
||||
|
||||
[features]
|
||||
#! The crate provides a set of optional features that can be enabled in your `cargo.toml` file.
|
||||
|
|
|
@ -198,10 +198,44 @@ impl Rect {
|
|||
.try_into()
|
||||
.expect("invalid number of rects")
|
||||
}
|
||||
|
||||
/// Clamp this rect to fit inside the other rect.
|
||||
///
|
||||
/// If the width or height of this rect is larger than the other rect, it will be clamped to the
|
||||
/// other rect's width or height.
|
||||
///
|
||||
/// If the left or top coordinate of this rect is smaller than the other rect, it will be
|
||||
/// clamped to the other rect's left or top coordinate.
|
||||
///
|
||||
/// If the right or bottom coordinate of this rect is larger than the other rect, it will be
|
||||
/// clamped to the other rect's right or bottom coordinate.
|
||||
///
|
||||
/// This is different from [`Rect::intersection`] because it will move this rect to fit inside
|
||||
/// the other rect, while [`Rect::intersection`] instead would keep this rect's position and
|
||||
/// truncate its size to only that which is inside the other rect.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::prelude::*;
|
||||
/// # fn render(frame: &mut Frame) {
|
||||
/// let area = frame.size();
|
||||
/// let rect = Rect::new(0, 0, 100, 100).clamp(area);
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn clamp(self, other: Rect) -> Rect {
|
||||
let width = self.width.min(other.width);
|
||||
let height = self.height.min(other.height);
|
||||
let x = self.x.clamp(other.x, other.right().saturating_sub(width));
|
||||
let y = self.y.clamp(other.y, other.bottom().saturating_sub(height));
|
||||
Rect::new(x, y, width, height)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
@ -399,4 +433,22 @@ mod tests {
|
|||
let layout = Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]);
|
||||
let [_a, _b, _c] = Rect::new(0, 0, 2, 1).split(&layout);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::inside(Rect::new(20, 20, 10, 10), Rect::new(20, 20, 10, 10))]
|
||||
#[case::up_left(Rect::new(5, 5, 10, 10), Rect::new(10, 10, 10, 10))]
|
||||
#[case::up(Rect::new(20, 5, 10, 10), Rect::new(20, 10, 10, 10))]
|
||||
#[case::up_right(Rect::new(105, 5, 10, 10), Rect::new(100, 10, 10, 10))]
|
||||
#[case::left(Rect::new(5, 20, 10, 10), Rect::new(10, 20, 10, 10))]
|
||||
#[case::right(Rect::new(105, 20, 10, 10), Rect::new(100, 20, 10, 10))]
|
||||
#[case::down_left(Rect::new(5, 105, 10, 10), Rect::new(10, 100, 10, 10))]
|
||||
#[case::down(Rect::new(20, 105, 10, 10), Rect::new(20, 100, 10, 10))]
|
||||
#[case::down_right(Rect::new(105, 105, 10, 10), Rect::new(100, 100, 10, 10))]
|
||||
#[case::too_wide(Rect::new(5, 20, 200, 10), Rect::new(10, 20, 100, 10))]
|
||||
#[case::too_tall(Rect::new(20, 5, 10, 200), Rect::new(20, 10, 10, 100))]
|
||||
#[case::too_large(Rect::new(0, 0, 200, 200), Rect::new(10, 10, 100, 100))]
|
||||
fn clamp(#[case] rect: Rect, #[case] expected: Rect) {
|
||||
let other = Rect::new(10, 10, 100, 100);
|
||||
assert_eq!(rect.clamp(other), expected);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue