1#![doc = include_str!("../readme.md")]
2
3use ratatui::layout::{Position, Rect};
4
5pub trait RelocatableState {
15 fn relocate(&mut self, shift: (i16, i16), clip: Rect);
17}
18
19#[macro_export]
22macro_rules! impl_relocatable_state {
23 ($($n:ident),* for $ty:ty) => {
24 impl $crate::RelocatableState for $ty {
25 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
26 $(self.$n.relocate(shift, clip);)*
27 }
28 }
29 };
30}
31
32impl RelocatableState for Rect {
33 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
34 *self = relocate_area(*self, shift, clip);
35 }
36}
37
38impl RelocatableState for [Rect] {
39 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
40 for rect in self {
41 rect.relocate(shift, clip);
42 }
43 }
44}
45
46pub fn relocate_areas(area: &mut [Rect], shift: (i16, i16), clip: Rect) {
48 for a in area {
49 *a = relocate_area(*a, shift, clip)
50 }
51}
52
53pub fn relocate_area(area: Rect, shift: (i16, i16), clip: Rect) -> Rect {
55 let relocated = relocate(area, shift);
56 let clipped = clipped(relocated, clip);
57
58 if !clipped.is_empty() {
59 clipped
60 } else {
61 Rect::default()
63 }
64}
65
66pub fn relocate_positions(pos: &mut [Position], shift: (i16, i16), clip: Rect) {
69 for p in pos {
70 *p = relocate_position(*p, shift, clip).unwrap_or_default()
71 }
72}
73
74pub fn relocate_position(pos: Position, shift: (i16, i16), clip: Rect) -> Option<Position> {
77 let reloc = relocate(Rect::new(pos.x, pos.y, 0, 0), shift);
78 let reloc_pos = Position::new(reloc.x, reloc.y);
79
80 if clip.contains(reloc_pos) {
81 Some(reloc_pos)
82 } else {
83 None
84 }
85}
86
87pub fn relocate_dark_offset(area: Rect, shift: (i16, i16), clip: Rect) -> (u16, u16) {
92 let relocated = relocate(area, shift);
93 let clipped = clipped(relocated, clip);
94
95 (clipped.x - relocated.x, clipped.y - relocated.y)
96}
97
98pub fn rect_dbg(area: Rect) -> String {
99 use std::fmt::Write;
100 let mut buf = String::new();
101 _ = write!(buf, "{}:{}+{}+{}", area.x, area.y, area.width, area.height);
102 buf
103}
104
105#[inline]
106fn relocate(area: Rect, shift: (i16, i16)) -> Rect {
107 let x0 = area.left().saturating_add_signed(shift.0);
108 let x1 = area.right().saturating_add_signed(shift.0);
109 let y0 = area.top().saturating_add_signed(shift.1);
110 let y1 = area.bottom().saturating_add_signed(shift.1);
111
112 Rect::new(x0, y0, x1 - x0, y1 - y0)
113}
114
115#[inline]
116fn clipped(area: Rect, clip: Rect) -> Rect {
117 area.intersection(clip)
118}