1#![doc = include_str!("../readme.md")]
2
3use ratatui_core::layout::{Position, Rect};
4use std::cmp::max;
5
6pub trait RelocatableState {
18 fn relocate(&mut self, shift: (i16, i16), clip: Rect);
22
23 #[allow(unused_variables)]
27 fn relocate_popup(&mut self, shift: (i16, i16), clip: Rect) {}
28
29 fn relocate_popup_hidden(&mut self) {
31 self.relocate_popup((0, 0), Rect::default())
32 }
33
34 fn relocate_hidden(&mut self) {
36 self.relocate((0, 0), Rect::default())
37 }
38}
39
40#[macro_export]
43macro_rules! impl_relocatable_state {
44 ($($n:ident),* for $ty:ty) => {
45 impl $crate::RelocatableState for $ty {
46 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
47 $(self.$n.relocate(shift, clip);)*
48 }
49 }
50 };
51}
52
53impl RelocatableState for Rect {
54 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
55 *self = relocate_area(*self, shift, clip);
56 }
57}
58
59impl RelocatableState for [Rect] {
60 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
61 for rect in self {
62 rect.relocate(shift, clip);
63 }
64 }
65}
66
67impl RelocatableState for () {
68 fn relocate(&mut self, _shift: (i16, i16), _clip: Rect) {}
69}
70
71pub fn relocate_areas(area: &mut [Rect], shift: (i16, i16), clip: Rect) {
73 for a in area {
74 *a = relocate_area(*a, shift, clip)
75 }
76}
77
78pub fn relocate_area(area: Rect, shift: (i16, i16), clip: Rect) -> Rect {
80 let relocated = relocate(area, shift);
81 let clipped = clipped(relocated, clip);
82
83 if !clipped.is_empty() {
84 clipped
85 } else {
86 Rect::default()
88 }
89}
90
91pub fn relocate_positions(pos: &mut [Position], shift: (i16, i16), clip: Rect) {
94 for p in pos {
95 *p = relocate_position(*p, shift, clip).unwrap_or_default()
96 }
97}
98
99pub fn relocate_position(pos: Position, shift: (i16, i16), clip: Rect) -> Option<Position> {
102 let reloc = relocate(Rect::new(pos.x, pos.y, 0, 0), shift);
103 let reloc_pos = Position::new(reloc.x, reloc.y);
104
105 if clip.contains(reloc_pos) {
106 Some(reloc_pos)
107 } else {
108 None
109 }
110}
111
112pub fn relocate_pos_tuple(pos: (u16, u16), shift: (i16, i16), clip: Rect) -> Option<(u16, u16)> {
115 let reloc = relocate(Rect::new(pos.0, pos.1, 0, 0), shift);
116 let reloc_pos = Position::new(reloc.x, reloc.y);
117
118 if clip.contains(reloc_pos) {
119 Some((reloc_pos.x, reloc_pos.y))
120 } else {
121 None
122 }
123}
124
125pub fn relocate_pos_tuple_opt(
128 pos: Option<(u16, u16)>,
129 shift: (i16, i16),
130 clip: Rect,
131) -> Option<(u16, u16)> {
132 let Some(pos) = pos else { return None };
133 relocate_pos_tuple(pos, shift, clip)
134}
135
136pub fn relocate_dark_offset(area: Rect, shift: (i16, i16), clip: Rect) -> (u16, u16) {
144 let rel_x0 = area.x as i16 + shift.0;
145 let rel_y0 = area.y as i16 + shift.1;
146 let clp_x0 = max(rel_x0, clip.x as i16);
147 let clp_y0 = max(rel_y0, clip.y as i16);
148
149 debug_assert!(clp_x0 - rel_x0 >= 0);
150 debug_assert!(clp_y0 - rel_y0 >= 0);
151
152 ((clp_x0 - rel_x0) as u16, (clp_y0 - rel_y0) as u16)
153}
154
155#[inline]
161fn relocate(area: Rect, shift: (i16, i16)) -> Rect {
162 let x0 = area.left().saturating_add_signed(shift.0);
163 let x1 = area.right().saturating_add_signed(shift.0);
164 let y0 = area.top().saturating_add_signed(shift.1);
165 let y1 = area.bottom().saturating_add_signed(shift.1);
166
167 Rect::new(x0, y0, x1 - x0, y1 - y0)
168}
169
170#[inline]
171fn clipped(area: Rect, clip: Rect) -> Rect {
172 area.intersection(clip)
173}