1use rat_reloc::RelocatableState;
41use rat_text::HasScreenCursor;
42use ratatui::buffer::Buffer;
43use ratatui::layout::{Constraint, Flex, Layout, Rect};
44use ratatui::widgets::{StatefulWidget, Widget};
45use std::marker::PhantomData;
46use std::rc::Rc;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum PairSplit {
51 Fix(u16, u16),
53 Fix1(u16),
56 Fix2(u16),
59 Ratio(u16, u16),
61 Constrain(Constraint, Constraint),
63}
64
65#[derive(Debug)]
67pub struct Paired<'a, T, U> {
68 first: T,
69 second: U,
70 split: PairSplit,
71 spacing: u16,
72 flex: Flex,
73 phantom: PhantomData<&'a ()>,
74}
75
76#[derive(Debug)]
77pub struct PairedState<'a, TS, US> {
78 pub first: &'a mut TS,
79 pub second: &'a mut US,
80}
81
82impl<T, U> Paired<'_, T, U> {
83 pub fn new(first: T, second: U) -> Self {
84 Self {
85 first,
86 second,
87 split: PairSplit::Ratio(1, 1),
88 spacing: 1,
89 flex: Default::default(),
90 phantom: Default::default(),
91 }
92 }
93
94 pub fn split(mut self, split: PairSplit) -> Self {
95 self.split = split;
96 self
97 }
98
99 pub fn spacing(mut self, spacing: u16) -> Self {
100 self.spacing = spacing;
101 self
102 }
103
104 pub fn flex(mut self, flex: Flex) -> Self {
105 self.flex = flex;
106 self
107 }
108}
109
110impl<T, U> Paired<'_, T, U> {
111 fn layout(&self, area: Rect) -> Rc<[Rect]> {
112 match self.split {
113 PairSplit::Fix(a, b) => {
114 Layout::horizontal([Constraint::Length(a), Constraint::Length(b)])
115 .spacing(self.spacing)
116 .flex(self.flex)
117 .split(area) }
119 PairSplit::Fix1(a) => {
120 Layout::horizontal([Constraint::Length(a), Constraint::Fill(1)])
121 .spacing(self.spacing)
122 .flex(self.flex)
123 .split(area) }
125 PairSplit::Fix2(b) => {
126 Layout::horizontal([Constraint::Fill(1), Constraint::Length(b)])
127 .spacing(self.spacing)
128 .flex(self.flex)
129 .split(area) }
131 PairSplit::Ratio(a, b) => {
132 Layout::horizontal([Constraint::Fill(a), Constraint::Fill(b)])
133 .spacing(self.spacing)
134 .flex(self.flex)
135 .split(area) }
137 PairSplit::Constrain(a, b) => {
138 Layout::horizontal([a, b])
139 .spacing(self.spacing)
140 .flex(self.flex)
141 .split(area) }
143 }
144 }
145}
146
147impl<'a, T, U, TS, US> StatefulWidget for Paired<'a, T, U>
148where
149 T: StatefulWidget<State = TS>,
150 U: StatefulWidget<State = US>,
151 TS: 'a,
152 US: 'a,
153{
154 type State = PairedState<'a, TS, US>;
155
156 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
157 let l = self.layout(area);
158 self.first.render(l[0], buf, state.first);
159 self.second.render(l[1], buf, state.second);
160 }
161}
162
163impl<T, U> Widget for Paired<'_, T, U>
164where
165 T: Widget,
166 U: Widget,
167{
168 fn render(self, area: Rect, buf: &mut Buffer)
169 where
170 Self: Sized,
171 {
172 let l = self.layout(area);
173 self.first.render(l[0], buf);
174 self.second.render(l[1], buf);
175 }
176}
177
178impl<TS, US> HasScreenCursor for PairedState<'_, TS, US>
179where
180 TS: HasScreenCursor,
181 US: HasScreenCursor,
182{
183 fn screen_cursor(&self) -> Option<(u16, u16)> {
184 self.first.screen_cursor().or(self.second.screen_cursor())
185 }
186}
187
188impl<TS, US> RelocatableState for PairedState<'_, TS, US>
189where
190 TS: RelocatableState,
191 US: RelocatableState,
192{
193 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
194 self.first.relocate(shift, clip);
195 self.second.relocate(shift, clip);
196 }
197}
198
199impl<'a, TS, US> PairedState<'a, TS, US> {
200 pub fn new(first: &'a mut TS, second: &'a mut US) -> Self {
201 Self { first, second }
202 }
203}
204
205pub struct PairedWidget<'a, T> {
208 widget: T,
209 phantom: PhantomData<&'a ()>,
210}
211
212impl<'a, T> PairedWidget<'a, T> {
213 pub fn new(widget: T) -> Self {
214 Self {
215 widget,
216 phantom: Default::default(),
217 }
218 }
219}
220
221impl<'a, T> StatefulWidget for PairedWidget<'a, T>
222where
223 T: Widget,
224{
225 type State = ();
226
227 fn render(self, area: Rect, buf: &mut Buffer, _: &mut Self::State) {
228 self.widget.render(area, buf);
229 }
230}
231
232impl<'a, T> HasScreenCursor for PairedWidget<'a, T> {
233 fn screen_cursor(&self) -> Option<(u16, u16)> {
234 None
235 }
236}