1use rat_reloc::RelocatableState;
41use rat_text::HasScreenCursor;
42use ratatui_core::buffer::Buffer;
43use ratatui_core::layout::{Constraint, Flex, Layout, Rect};
44use ratatui_core::text::Span;
45use ratatui_core::widgets::{StatefulWidget, Widget};
46use std::marker::PhantomData;
47use std::rc::Rc;
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum PairSplit {
52 Fix(u16, u16),
54 Fix1(u16),
57 Fix2(u16),
60 Ratio(u16, u16),
62 Constrain(Constraint, Constraint),
64}
65
66#[derive(Debug)]
68pub struct Paired<'a, T, U> {
69 first: T,
70 second: U,
71 split: PairSplit,
72 spacing: u16,
73 flex: Flex,
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
83pub struct NSpan<'a> {
86 pub span: Span<'a>,
87}
88
89impl<'a, U> Paired<'a, NSpan<'a>, U> {
90 pub fn new_labeled(label: impl Into<Span<'a>>, second: U) -> Self {
92 let label = label.into();
93 let width = label.width();
94 Self {
95 first: NSpan { span: label },
96 second,
97 split: PairSplit::Fix1(width as u16),
98 spacing: 1,
99 flex: Default::default(),
100 phantom: Default::default(),
101 }
102 }
103}
104
105impl<'a, T, U> Paired<'a, T, U> {
106 pub fn new(first: T, second: U) -> Self {
107 Self {
108 first,
109 second,
110 split: PairSplit::Ratio(1, 1),
111 spacing: 1,
112 flex: Default::default(),
113 phantom: Default::default(),
114 }
115 }
116
117 pub fn split(mut self, split: PairSplit) -> Self {
118 self.split = split;
119 self
120 }
121
122 pub fn spacing(mut self, spacing: u16) -> Self {
123 self.spacing = spacing;
124 self
125 }
126
127 pub fn flex(mut self, flex: Flex) -> Self {
128 self.flex = flex;
129 self
130 }
131}
132
133impl<T, U> Paired<'_, T, U> {
134 fn layout(&self, area: Rect) -> Rc<[Rect]> {
135 match self.split {
136 PairSplit::Fix(a, b) => {
137 Layout::horizontal([Constraint::Length(a), Constraint::Length(b)])
138 .spacing(self.spacing)
139 .flex(self.flex)
140 .split(area) }
142 PairSplit::Fix1(a) => {
143 Layout::horizontal([Constraint::Length(a), Constraint::Fill(1)])
144 .spacing(self.spacing)
145 .flex(self.flex)
146 .split(area) }
148 PairSplit::Fix2(b) => {
149 Layout::horizontal([Constraint::Fill(1), Constraint::Length(b)])
150 .spacing(self.spacing)
151 .flex(self.flex)
152 .split(area) }
154 PairSplit::Ratio(a, b) => {
155 Layout::horizontal([Constraint::Fill(a), Constraint::Fill(b)])
156 .spacing(self.spacing)
157 .flex(self.flex)
158 .split(area) }
160 PairSplit::Constrain(a, b) => {
161 Layout::horizontal([a, b])
162 .spacing(self.spacing)
163 .flex(self.flex)
164 .split(area) }
166 }
167 }
168}
169
170impl<'a, U, US> StatefulWidget for Paired<'a, NSpan<'a>, U>
171where
172 U: StatefulWidget<State = US>,
173 US: 'a,
174{
175 type State = US;
176
177 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
178 let l = self.layout(area);
179 self.first.span.render(l[0], buf);
180 self.second.render(l[1], buf, state);
181 }
182}
183
184impl<'a, T, U, TS, US> StatefulWidget for Paired<'a, T, U>
185where
186 T: StatefulWidget<State = TS>,
187 U: StatefulWidget<State = US>,
188 TS: 'a,
189 US: 'a,
190{
191 type State = PairedState<'a, TS, US>;
192
193 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
194 let l = self.layout(area);
195 self.first.render(l[0], buf, state.first);
196 self.second.render(l[1], buf, state.second);
197 }
198}
199
200impl<'a, U> Widget for Paired<'a, NSpan<'a>, U>
201where
202 U: Widget,
203{
204 fn render(self, area: Rect, buf: &mut Buffer) {
205 let l = self.layout(area);
206 self.first.span.render(l[0], buf);
207 self.second.render(l[1], buf);
208 }
209}
210
211impl<T, U> Widget for Paired<'_, T, U>
212where
213 T: Widget,
214 U: Widget,
215{
216 fn render(self, area: Rect, buf: &mut Buffer)
217 where
218 Self: Sized,
219 {
220 let l = self.layout(area);
221 self.first.render(l[0], buf);
222 self.second.render(l[1], buf);
223 }
224}
225
226impl<TS, US> HasScreenCursor for PairedState<'_, TS, US>
227where
228 TS: HasScreenCursor,
229 US: HasScreenCursor,
230{
231 fn screen_cursor(&self) -> Option<(u16, u16)> {
232 self.first.screen_cursor().or(self.second.screen_cursor())
233 }
234}
235
236impl<TS, US> RelocatableState for PairedState<'_, TS, US>
237where
238 TS: RelocatableState,
239 US: RelocatableState,
240{
241 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
242 self.first.relocate(shift, clip);
243 self.second.relocate(shift, clip);
244 }
245}
246
247impl<'a, TS, US> PairedState<'a, TS, US> {
248 pub fn new(first: &'a mut TS, second: &'a mut US) -> Self {
249 Self { first, second }
250 }
251}
252
253#[derive(Debug)]
256pub struct PairedWidget<'a, T> {
257 widget: T,
258 phantom: PhantomData<&'a ()>,
259}
260
261impl<'a, T> PairedWidget<'a, T> {
262 pub fn new(widget: T) -> Self {
263 Self {
264 widget,
265 phantom: Default::default(),
266 }
267 }
268}
269
270impl<'a, T> StatefulWidget for PairedWidget<'a, T>
271where
272 T: Widget,
273{
274 type State = ();
275
276 fn render(self, area: Rect, buf: &mut Buffer, _: &mut Self::State) {
277 self.widget.render(area, buf);
278 }
279}
280
281impl<'a, T> HasScreenCursor for PairedWidget<'a, T> {
282 fn screen_cursor(&self) -> Option<(u16, u16)> {
283 None
284 }
285}