use rat_reloc::RelocatableState;
use rat_text::HasScreenCursor;
use ratatui_core::buffer::Buffer;
use ratatui_core::layout::{Constraint, Flex, Layout, Rect};
use ratatui_core::widgets::{StatefulWidget, Widget};
use std::marker::PhantomData;
use std::rc::Rc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PairSplit {
Fix(u16, u16),
Fix1(u16),
Fix2(u16),
Ratio(u16, u16),
Constrain(Constraint, Constraint),
}
#[derive(Debug)]
pub struct Paired<'a, T, U> {
first: T,
second: U,
split: PairSplit,
spacing: u16,
flex: Flex,
phantom: PhantomData<&'a ()>,
}
#[derive(Debug)]
pub struct PairedState<'a, TS, US> {
pub first: &'a mut TS,
pub second: &'a mut US,
}
impl<T, U> Paired<'_, T, U> {
pub fn new(first: T, second: U) -> Self {
Self {
first,
second,
split: PairSplit::Ratio(1, 1),
spacing: 1,
flex: Default::default(),
phantom: Default::default(),
}
}
pub fn split(mut self, split: PairSplit) -> Self {
self.split = split;
self
}
pub fn spacing(mut self, spacing: u16) -> Self {
self.spacing = spacing;
self
}
pub fn flex(mut self, flex: Flex) -> Self {
self.flex = flex;
self
}
}
impl<T, U> Paired<'_, T, U> {
fn layout(&self, area: Rect) -> Rc<[Rect]> {
match self.split {
PairSplit::Fix(a, b) => {
Layout::horizontal([Constraint::Length(a), Constraint::Length(b)])
.spacing(self.spacing)
.flex(self.flex)
.split(area) }
PairSplit::Fix1(a) => {
Layout::horizontal([Constraint::Length(a), Constraint::Fill(1)])
.spacing(self.spacing)
.flex(self.flex)
.split(area) }
PairSplit::Fix2(b) => {
Layout::horizontal([Constraint::Fill(1), Constraint::Length(b)])
.spacing(self.spacing)
.flex(self.flex)
.split(area) }
PairSplit::Ratio(a, b) => {
Layout::horizontal([Constraint::Fill(a), Constraint::Fill(b)])
.spacing(self.spacing)
.flex(self.flex)
.split(area) }
PairSplit::Constrain(a, b) => {
Layout::horizontal([a, b])
.spacing(self.spacing)
.flex(self.flex)
.split(area) }
}
}
}
impl<'a, T, U, TS, US> StatefulWidget for Paired<'a, T, U>
where
T: StatefulWidget<State = TS>,
U: StatefulWidget<State = US>,
TS: 'a,
US: 'a,
{
type State = PairedState<'a, TS, US>;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let l = self.layout(area);
self.first.render(l[0], buf, state.first);
self.second.render(l[1], buf, state.second);
}
}
impl<T, U> Widget for Paired<'_, T, U>
where
T: Widget,
U: Widget,
{
fn render(self, area: Rect, buf: &mut Buffer)
where
Self: Sized,
{
let l = self.layout(area);
self.first.render(l[0], buf);
self.second.render(l[1], buf);
}
}
impl<TS, US> HasScreenCursor for PairedState<'_, TS, US>
where
TS: HasScreenCursor,
US: HasScreenCursor,
{
fn screen_cursor(&self) -> Option<(u16, u16)> {
self.first.screen_cursor().or(self.second.screen_cursor())
}
}
impl<TS, US> RelocatableState for PairedState<'_, TS, US>
where
TS: RelocatableState,
US: RelocatableState,
{
fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
self.first.relocate(shift, clip);
self.second.relocate(shift, clip);
}
}
impl<'a, TS, US> PairedState<'a, TS, US> {
pub fn new(first: &'a mut TS, second: &'a mut US) -> Self {
Self { first, second }
}
}
#[derive(Debug)]
pub struct PairedWidget<'a, T> {
widget: T,
phantom: PhantomData<&'a ()>,
}
impl<'a, T> PairedWidget<'a, T> {
pub fn new(widget: T) -> Self {
Self {
widget,
phantom: Default::default(),
}
}
}
impl<'a, T> StatefulWidget for PairedWidget<'a, T>
where
T: Widget,
{
type State = ();
fn render(self, area: Rect, buf: &mut Buffer, _: &mut Self::State) {
self.widget.render(area, buf);
}
}
impl<'a, T> HasScreenCursor for PairedWidget<'a, T> {
fn screen_cursor(&self) -> Option<(u16, u16)> {
None
}
}