1#![doc = include_str!("../readme.md")]
2
3use ratatui::layout::{Position, Rect};
4
5pub trait RelocatableState {
17 fn relocate(&mut self, shift: (i16, i16), clip: Rect);
21
22 fn relocate_hidden(&mut self) {
24 self.relocate((0, 0), Rect::default())
25 }
26}
27
28#[macro_export]
31macro_rules! impl_relocatable_state {
32 ($($n:ident),* for $ty:ty) => {
33 impl $crate::RelocatableState for $ty {
34 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
35 $(self.$n.relocate(shift, clip);)*
36 }
37 }
38 };
39}
40
41impl RelocatableState for Rect {
42 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
43 *self = relocate_area(*self, shift, clip);
44 }
45}
46
47impl RelocatableState for [Rect] {
48 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
49 for rect in self {
50 rect.relocate(shift, clip);
51 }
52 }
53}
54
55impl RelocatableState for () {
56 fn relocate(&mut self, _shift: (i16, i16), _clip: Rect) {}
57}
58
59pub fn relocate_areas(area: &mut [Rect], shift: (i16, i16), clip: Rect) {
61 for a in area {
62 *a = relocate_area(*a, shift, clip)
63 }
64}
65
66pub fn relocate_area(area: Rect, shift: (i16, i16), clip: Rect) -> Rect {
68 let relocated = relocate(area, shift);
69 let clipped = clipped(relocated, clip);
70
71 if !clipped.is_empty() {
72 clipped
73 } else {
74 Rect::default()
76 }
77}
78
79pub fn relocate_positions(pos: &mut [Position], shift: (i16, i16), clip: Rect) {
82 for p in pos {
83 *p = relocate_position(*p, shift, clip).unwrap_or_default()
84 }
85}
86
87pub fn relocate_position(pos: Position, shift: (i16, i16), clip: Rect) -> Option<Position> {
90 let reloc = relocate(Rect::new(pos.x, pos.y, 0, 0), shift);
91 let reloc_pos = Position::new(reloc.x, reloc.y);
92
93 if clip.contains(reloc_pos) {
94 Some(reloc_pos)
95 } else {
96 None
97 }
98}
99
100pub fn relocate_pos_tuple(pos: (u16, u16), shift: (i16, i16), clip: Rect) -> Option<(u16, u16)> {
103 let reloc = relocate(Rect::new(pos.0, pos.1, 0, 0), shift);
104 let reloc_pos = Position::new(reloc.x, reloc.y);
105
106 if clip.contains(reloc_pos) {
107 Some((reloc_pos.x, reloc_pos.y))
108 } else {
109 None
110 }
111}
112
113pub fn relocate_dark_offset(area: Rect, shift: (i16, i16), clip: Rect) -> (u16, u16) {
118 let relocated = relocate(area, shift);
119 let clipped = clipped(relocated, clip);
120
121 (clipped.x - relocated.x, clipped.y - relocated.y)
122}
123
124#[inline]
125fn relocate(area: Rect, shift: (i16, i16)) -> Rect {
126 let x0 = area.left().saturating_add_signed(shift.0);
127 let x1 = area.right().saturating_add_signed(shift.0);
128 let y0 = area.top().saturating_add_signed(shift.1);
129 let y1 = area.bottom().saturating_add_signed(shift.1);
130
131 Rect::new(x0, y0, x1 - x0, y1 - y0)
132}
133
134#[inline]
135fn clipped(area: Rect, clip: Rect) -> Rect {
136 area.intersection(clip)
137}