rat_reloc/
lib.rs

1#![doc = include_str!("../readme.md")]
2
3use ratatui::layout::{Position, Rect};
4
5/// Widgets can be rendered to a temporary buffer using
6/// the buffers own coordinate system.
7///
8/// To adjust the Rects derived during rendering/layout to
9/// the actual screen coordinates a widget can implement this
10/// trait.
11///
12/// Container widgets that support this will call relocate()
13/// after rendering the widgets.
14pub trait RelocatableState {
15    /// Relocate the areas in this widgets state.
16    fn relocate(&mut self, shift: (i16, i16), clip: Rect);
17}
18
19/// Create the implementation of RelocatableState for the
20/// given list of struct members.
21#[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
46impl RelocatableState for () {
47    fn relocate(&mut self, _shift: (i16, i16), _clip: Rect) {}
48}
49
50/// Shift the area by offset and clip it.
51pub fn relocate_areas(area: &mut [Rect], shift: (i16, i16), clip: Rect) {
52    for a in area {
53        *a = relocate_area(*a, shift, clip)
54    }
55}
56
57/// Shift the area by offset and clip it.
58pub fn relocate_area(area: Rect, shift: (i16, i16), clip: Rect) -> Rect {
59    let relocated = relocate(area, shift);
60    let clipped = clipped(relocated, clip);
61
62    if !clipped.is_empty() {
63        clipped
64    } else {
65        // default on empty
66        Rect::default()
67    }
68}
69
70/// Shift the position by offset and clip it.
71/// Clipped positions are replaced with (0,0) for this function.
72pub fn relocate_positions(pos: &mut [Position], shift: (i16, i16), clip: Rect) {
73    for p in pos {
74        *p = relocate_position(*p, shift, clip).unwrap_or_default()
75    }
76}
77
78/// Shift the position by offset and clip it.
79/// Returns None if the position is clipped.
80pub fn relocate_position(pos: Position, shift: (i16, i16), clip: Rect) -> Option<Position> {
81    let reloc = relocate(Rect::new(pos.x, pos.y, 0, 0), shift);
82    let reloc_pos = Position::new(reloc.x, reloc.y);
83
84    if clip.contains(reloc_pos) {
85        Some(reloc_pos)
86    } else {
87        None
88    }
89}
90
91/// Shift the position by offset and clip it.
92/// Returns None if the position is clipped.
93pub fn relocate_pos_tuple(pos: (u16, u16), shift: (i16, i16), clip: Rect) -> Option<(u16, u16)> {
94    let reloc = relocate(Rect::new(pos.0, pos.1, 0, 0), shift);
95    let reloc_pos = Position::new(reloc.x, reloc.y);
96
97    if clip.contains(reloc_pos) {
98        Some((reloc_pos.x, reloc_pos.y))
99    } else {
100        None
101    }
102}
103
104/// Clipping might introduce another offset by cutting away
105/// part of an area that a widgets internal offset refers to.
106///
107/// This calculates this dark, extra offset.
108pub fn relocate_dark_offset(area: Rect, shift: (i16, i16), clip: Rect) -> (u16, u16) {
109    let relocated = relocate(area, shift);
110    let clipped = clipped(relocated, clip);
111
112    (clipped.x - relocated.x, clipped.y - relocated.y)
113}
114
115pub fn rect_dbg(area: Rect) -> String {
116    use std::fmt::Write;
117    let mut buf = String::new();
118    _ = write!(buf, "{}:{}+{}+{}", area.x, area.y, area.width, area.height);
119    buf
120}
121
122#[inline]
123fn relocate(area: Rect, shift: (i16, i16)) -> Rect {
124    let x0 = area.left().saturating_add_signed(shift.0);
125    let x1 = area.right().saturating_add_signed(shift.0);
126    let y0 = area.top().saturating_add_signed(shift.1);
127    let y1 = area.bottom().saturating_add_signed(shift.1);
128
129    Rect::new(x0, y0, x1 - x0, y1 - y0)
130}
131
132#[inline]
133fn clipped(area: Rect, clip: Rect) -> Rect {
134    area.intersection(clip)
135}