1use crate::_private::NonExhaustive;
41use map_range_int::MapRange;
42use rat_reloc::RelocatableState;
43use rat_text::HasScreenCursor;
44use ratatui::buffer::Buffer;
45use ratatui::layout::Rect;
46use ratatui::widgets::{StatefulWidget, Widget};
47use std::cmp::min;
48use std::marker::PhantomData;
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum PairSplit {
53 Fix(u16, u16),
57 Fix1(u16),
60 Fix2(u16),
63 Ratio(u16, u16),
65}
66
67#[derive(Debug)]
69pub struct Paired<'a, T, U> {
70 first: T,
71 second: U,
72 split: PairSplit,
73 spacing: u16,
74 phantom: PhantomData<&'a ()>,
75}
76
77#[derive(Debug)]
78pub struct PairedState<'a, TS, US> {
79 pub first: &'a mut TS,
80 pub second: &'a mut US,
81
82 pub non_exhaustive: NonExhaustive,
83}
84
85impl<T, U> Paired<'_, T, U> {
86 pub fn new(first: T, second: U) -> Self {
87 Self {
88 first,
89 second,
90 split: PairSplit::Ratio(1, 1),
91 spacing: 1,
92 phantom: Default::default(),
93 }
94 }
95
96 pub fn split(mut self, split: PairSplit) -> Self {
97 self.split = split;
98 self
99 }
100
101 pub fn spacing(mut self, spacing: u16) -> Self {
102 self.spacing = spacing;
103 self
104 }
105}
106
107impl<T, U> Paired<'_, T, U> {
108 fn layout(&self, area: Rect) -> (u16, u16, u16) {
109 let mut sp = self.spacing;
110
111 match self.split {
112 PairSplit::Fix(a, b) => {
113 if a + sp + b > area.width {
114 let rest = area.width - (a + sp + b);
115 (a - rest / 2, sp, b - (rest - rest / 2))
116 } else {
117 let rest = (a + sp + b) - area.width;
118 (a + rest / 2, sp, b + (rest - rest / 2))
119 }
120 }
121 PairSplit::Fix1(a) => {
122 if a > area.width {
123 sp = 0;
124 (area.width, sp, 0)
125 } else {
126 (a, sp, area.width.saturating_sub(a + sp))
127 }
128 }
129 PairSplit::Fix2(b) => {
130 if b > area.width {
131 sp = 0;
132 (area.width, sp, 0)
133 } else {
134 (area.width.saturating_sub(b + sp), sp, b)
135 }
136 }
137 PairSplit::Ratio(a, b) => {
138 sp = min(sp, area.width);
139 (
140 a.map_range_unchecked((0, a + b), (0, area.width - sp)),
141 sp,
142 b.map_range_unchecked((0, a + b), (0, area.width - sp)),
143 )
144 }
145 }
146 }
147}
148
149impl<'a, T, U, TS, US> StatefulWidget for Paired<'a, T, U>
150where
151 T: StatefulWidget<State = TS>,
152 U: StatefulWidget<State = US>,
153 TS: 'a,
154 US: 'a,
155{
156 type State = PairedState<'a, TS, US>;
157
158 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
159 let (a, sp, b) = self.layout(area);
160
161 let area_a = Rect::new(area.x, area.y, a, area.height);
162 let area_b = Rect::new(area.x + a + sp, area.y, b, area.height);
163
164 self.first.render(area_a, buf, state.first);
165 self.second.render(area_b, buf, state.second);
166 }
167}
168
169impl<T, U> Widget for Paired<'_, T, U>
170where
171 T: Widget,
172 U: Widget,
173{
174 fn render(self, area: Rect, buf: &mut Buffer)
175 where
176 Self: Sized,
177 {
178 let (a, sp, b) = self.layout(area);
179
180 let area_a = Rect::new(area.x, area.y, a, area.height);
181 let area_b = Rect::new(area.x + a + sp, area.y, b, area.height);
182
183 self.first.render(area_a, buf);
184 self.second.render(area_b, buf);
185 }
186}
187
188impl<TS, US> HasScreenCursor for PairedState<'_, TS, US>
189where
190 TS: HasScreenCursor,
191 US: HasScreenCursor,
192{
193 fn screen_cursor(&self) -> Option<(u16, u16)> {
194 self.first.screen_cursor().or(self.second.screen_cursor())
195 }
196}
197
198impl<TS, US> RelocatableState for PairedState<'_, TS, US>
199where
200 TS: RelocatableState,
201 US: RelocatableState,
202{
203 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
204 self.first.relocate(shift, clip);
205 self.second.relocate(shift, clip);
206 }
207}
208
209impl<'a, TS, US> PairedState<'a, TS, US> {
210 pub fn new(first: &'a mut TS, second: &'a mut US) -> Self {
211 Self {
212 first,
213 second,
214 non_exhaustive: NonExhaustive,
215 }
216 }
217}
218
219pub struct PairedWidget<'a, T> {
222 widget: T,
223 phantom: PhantomData<&'a ()>,
224}
225
226impl<'a, T> PairedWidget<'a, T> {
227 pub fn new(widget: T) -> Self {
228 Self {
229 widget,
230 phantom: Default::default(),
231 }
232 }
233}
234
235impl<'a, T> StatefulWidget for PairedWidget<'a, T>
236where
237 T: Widget,
238{
239 type State = ();
240
241 fn render(self, area: Rect, buf: &mut Buffer, _: &mut Self::State) {
242 self.widget.render(area, buf);
243 }
244}
245
246impl<'a, T> HasScreenCursor for PairedWidget<'a, T> {
247 fn screen_cursor(&self) -> Option<(u16, u16)> {
248 None
249 }
250}