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 that will
6/// be dumped to the main render Buffer at a later point.
7///
8/// This temporary Buffer can have its own coordinate system
9/// and output in the main render Buffer can happen anywhere.
10///
11/// Most rat-widgets store one or more areas during rendering
12/// for later mouse interactions. All these areas need to
13/// be adjusted whenever such a temporary Buffer is used.
14///
15/// This trait provides the entry point for such an adjustment.
16pub trait RelocatableState {
17    /// Relocate the areas in this widgets state.
18    /// - shift: positions are moved by (x,y)
19    /// - clip: areas must be clipped to the given Rect.
20    fn relocate(&mut self, shift: (i16, i16), clip: Rect);
21}
22
23/// Create the implementation of RelocatableState for the
24/// given list of struct members.
25#[macro_export]
26macro_rules! impl_relocatable_state {
27    ($($n:ident),* for $ty:ty) => {
28        impl $crate::RelocatableState for $ty {
29            fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
30                $(self.$n.relocate(shift, clip);)*
31            }
32        }
33    };
34}
35
36impl RelocatableState for Rect {
37    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
38        *self = relocate_area(*self, shift, clip);
39    }
40}
41
42impl RelocatableState for [Rect] {
43    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
44        for rect in self {
45            rect.relocate(shift, clip);
46        }
47    }
48}
49
50impl RelocatableState for () {
51    fn relocate(&mut self, _shift: (i16, i16), _clip: Rect) {}
52}
53
54/// Shift the area by offset and clip it.
55pub fn relocate_areas(area: &mut [Rect], shift: (i16, i16), clip: Rect) {
56    for a in area {
57        *a = relocate_area(*a, shift, clip)
58    }
59}
60
61/// Shift the area by offset and clip it.
62pub fn relocate_area(area: Rect, shift: (i16, i16), clip: Rect) -> Rect {
63    let relocated = relocate(area, shift);
64    let clipped = clipped(relocated, clip);
65
66    if !clipped.is_empty() {
67        clipped
68    } else {
69        // default on empty
70        Rect::default()
71    }
72}
73
74/// Shift the position by offset and clip it.
75/// Clipped positions are replaced with (0,0) for this function.
76pub fn relocate_positions(pos: &mut [Position], shift: (i16, i16), clip: Rect) {
77    for p in pos {
78        *p = relocate_position(*p, shift, clip).unwrap_or_default()
79    }
80}
81
82/// Shift the position by offset and clip it.
83/// Returns None if the position is clipped.
84pub fn relocate_position(pos: Position, shift: (i16, i16), clip: Rect) -> Option<Position> {
85    let reloc = relocate(Rect::new(pos.x, pos.y, 0, 0), shift);
86    let reloc_pos = Position::new(reloc.x, reloc.y);
87
88    if clip.contains(reloc_pos) {
89        Some(reloc_pos)
90    } else {
91        None
92    }
93}
94
95/// Shift the position by offset and clip it.
96/// Returns None if the position is clipped.
97pub fn relocate_pos_tuple(pos: (u16, u16), shift: (i16, i16), clip: Rect) -> Option<(u16, u16)> {
98    let reloc = relocate(Rect::new(pos.0, pos.1, 0, 0), shift);
99    let reloc_pos = Position::new(reloc.x, reloc.y);
100
101    if clip.contains(reloc_pos) {
102        Some((reloc_pos.x, reloc_pos.y))
103    } else {
104        None
105    }
106}
107
108/// Clipping might introduce another offset by cutting away
109/// part of an area that a widgets internal offset refers to.
110///
111/// This calculates this dark, extra offset.
112pub fn relocate_dark_offset(area: Rect, shift: (i16, i16), clip: Rect) -> (u16, u16) {
113    let relocated = relocate(area, shift);
114    let clipped = clipped(relocated, clip);
115
116    (clipped.x - relocated.x, clipped.y - relocated.y)
117}
118
119#[inline]
120fn relocate(area: Rect, shift: (i16, i16)) -> Rect {
121    let x0 = area.left().saturating_add_signed(shift.0);
122    let x1 = area.right().saturating_add_signed(shift.0);
123    let y0 = area.top().saturating_add_signed(shift.1);
124    let y1 = area.bottom().saturating_add_signed(shift.1);
125
126    Rect::new(x0, y0, x1 - x0, y1 - y0)
127}
128
129#[inline]
130fn clipped(area: Rect, clip: Rect) -> Rect {
131    area.intersection(clip)
132}