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 #[allow(unused_variables)]
26 fn relocate_popup(&mut self, shift: (i16, i16), clip: Rect) {}
27
28 fn relocate_popup_hidden(&mut self) {
30 self.relocate_popup((0, 0), Rect::default())
31 }
32
33 fn relocate_hidden(&mut self) {
35 self.relocate((0, 0), Rect::default())
36 }
37}
38
39#[macro_export]
42macro_rules! impl_relocatable_state {
43 ($($n:ident),* for $ty:ty) => {
44 impl $crate::RelocatableState for $ty {
45 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
46 $(self.$n.relocate(shift, clip);)*
47 }
48 }
49 };
50}
51
52impl RelocatableState for Rect {
53 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
54 *self = relocate_area(*self, shift, clip);
55 }
56}
57
58impl RelocatableState for [Rect] {
59 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
60 for rect in self {
61 rect.relocate(shift, clip);
62 }
63 }
64}
65
66impl RelocatableState for () {
67 fn relocate(&mut self, _shift: (i16, i16), _clip: Rect) {}
68}
69
70pub fn relocate_areas(area: &mut [Rect], shift: (i16, i16), clip: Rect) {
72 for a in area {
73 *a = relocate_area(*a, shift, clip)
74 }
75}
76
77pub fn relocate_area(area: Rect, shift: (i16, i16), clip: Rect) -> Rect {
79 let relocated = relocate(area, shift);
80 let clipped = clipped(relocated, clip);
81
82 if !clipped.is_empty() {
83 clipped
84 } else {
85 Rect::default()
87 }
88}
89
90pub fn relocate_positions(pos: &mut [Position], shift: (i16, i16), clip: Rect) {
93 for p in pos {
94 *p = relocate_position(*p, shift, clip).unwrap_or_default()
95 }
96}
97
98pub fn relocate_position(pos: Position, shift: (i16, i16), clip: Rect) -> Option<Position> {
101 let reloc = relocate(Rect::new(pos.x, pos.y, 0, 0), shift);
102 let reloc_pos = Position::new(reloc.x, reloc.y);
103
104 if clip.contains(reloc_pos) {
105 Some(reloc_pos)
106 } else {
107 None
108 }
109}
110
111pub fn relocate_pos_tuple(pos: (u16, u16), shift: (i16, i16), clip: Rect) -> Option<(u16, u16)> {
114 let reloc = relocate(Rect::new(pos.0, pos.1, 0, 0), shift);
115 let reloc_pos = Position::new(reloc.x, reloc.y);
116
117 if clip.contains(reloc_pos) {
118 Some((reloc_pos.x, reloc_pos.y))
119 } else {
120 None
121 }
122}
123
124pub fn relocate_dark_offset(area: Rect, shift: (i16, i16), clip: Rect) -> (u16, u16) {
129 let relocated = relocate(area, shift);
130 let clipped = clipped(relocated, clip);
131
132 (clipped.x - relocated.x, clipped.y - relocated.y)
133}
134
135#[inline]
136fn relocate(area: Rect, shift: (i16, i16)) -> Rect {
137 let x0 = area.left().saturating_add_signed(shift.0);
138 let x1 = area.right().saturating_add_signed(shift.0);
139 let y0 = area.top().saturating_add_signed(shift.1);
140 let y1 = area.bottom().saturating_add_signed(shift.1);
141
142 Rect::new(x0, y0, x1 - x0, y1 - y0)
143}
144
145#[inline]
146fn clipped(area: Rect, clip: Rect) -> Rect {
147 area.intersection(clip)
148}