use crate::tabbed::{TabPlacement, TabWidget, Tabbed, TabbedState};
use crate::util::revert_style;
use ratatui_core::buffer::Buffer;
use ratatui_core::layout::{Constraint, Flex, Layout, Margin, Rect};
use ratatui_core::text::Span;
use ratatui_core::widgets::Widget;
#[derive(Debug, Default)]
pub(super) struct GluedTabs;
impl TabWidget for GluedTabs {
fn layout(&self, area: Rect, tabbed: &Tabbed<'_>, state: &mut TabbedState) {
state.area = area;
let margin_offset = 1 + if tabbed.block.is_some() { 1 } else { 0 };
let close_width = if tabbed.closeable { 2 } else { 0 };
let max_width = tabbed
.tabs
.iter()
.map(|v| v.width())
.max()
.unwrap_or_default() as u16;
match tabbed.placement {
TabPlacement::Top => {
state.block_area = Rect::new(area.x, area.y + 1, area.width, area.height - 1);
state.tab_title_area = Rect::new(area.x, area.y, area.width, 1);
if let Some(block) = &tabbed.block {
state.widget_area = block.inner(state.block_area);
} else {
state.widget_area = state.block_area;
}
}
TabPlacement::Bottom => {
state.block_area =
Rect::new(area.x, area.y, area.width, area.height.saturating_sub(1));
state.tab_title_area = Rect::new(
area.x,
area.y + area.height.saturating_sub(1),
area.width,
1,
);
if let Some(block) = &tabbed.block {
state.widget_area = block.inner(state.block_area);
} else {
state.widget_area = state.block_area;
}
}
TabPlacement::Left => {
state.block_area = Rect::new(
area.x + max_width + 2 + close_width,
area.y,
area.width - (max_width + 2 + close_width),
area.height,
);
state.tab_title_area =
Rect::new(area.x, area.y, max_width + 2 + close_width, area.height);
if let Some(block) = &tabbed.block {
state.widget_area = block.inner(state.block_area);
} else {
state.widget_area = state.block_area;
}
}
TabPlacement::Right => {
state.block_area = Rect::new(
area.x,
area.y,
area.width - (max_width + 2 + close_width),
area.height,
);
state.tab_title_area = Rect::new(
area.x + area.width - (max_width + 2 + close_width),
area.y,
max_width + 2 + close_width,
area.height,
);
if let Some(block) = &tabbed.block {
state.widget_area = block.inner(state.block_area);
} else {
state.widget_area = state.block_area;
}
}
}
match tabbed.placement {
TabPlacement::Top | TabPlacement::Bottom => {
let mut constraints = Vec::new();
for tab in tabbed.tabs.iter() {
constraints.push(Constraint::Length(tab.width() as u16 + 2 + close_width));
}
state.tab_title_areas = Vec::from(
Layout::horizontal(constraints)
.flex(Flex::Start)
.spacing(1)
.horizontal_margin(margin_offset)
.split(state.tab_title_area)
.as_ref(),
);
}
TabPlacement::Left | TabPlacement::Right => {
let mut constraints = Vec::new();
for _tab in tabbed.tabs.iter() {
constraints.push(Constraint::Length(1));
}
state.tab_title_areas = Vec::from(
Layout::vertical(constraints)
.flex(Flex::Start)
.vertical_margin(margin_offset)
.split(state.tab_title_area)
.as_ref(),
);
}
}
match tabbed.placement {
TabPlacement::Top | TabPlacement::Bottom => {
state.tab_title_close_areas = state
.tab_title_areas
.iter()
.map(|v| {
Rect::new(
(v.x + v.width)
.saturating_sub(close_width)
.saturating_sub(1),
v.y,
if tabbed.closeable { 3 } else { 0 },
1,
)
})
.collect::<Vec<_>>();
}
TabPlacement::Left => {
state.tab_title_close_areas = state
.tab_title_areas
.iter()
.map(|v| {
Rect::new(
v.x, v.y,
if tabbed.closeable { 3 } else { 0 },
1,
)
})
.collect::<Vec<_>>();
}
TabPlacement::Right => {
state.tab_title_close_areas = state
.tab_title_areas
.iter()
.map(|v| {
Rect::new(
(v.x + v.width)
.saturating_sub(close_width)
.saturating_sub(1),
v.y,
if tabbed.closeable { 3 } else { 0 },
1,
)
})
.collect::<Vec<_>>();
}
}
}
fn render(&self, buf: &mut Buffer, tabbed: &Tabbed<'_>, state: &mut TabbedState) {
let focus_style = if let Some(focus_style) = tabbed.focus_style {
focus_style
} else {
revert_style(tabbed.style)
};
let select_style = if let Some(select_style) = tabbed.select_style {
if state.focus.get() {
focus_style
} else {
select_style
}
} else {
if state.focus.get() {
focus_style
} else {
revert_style(tabbed.style)
}
};
let tab_style = if let Some(tab_style) = tabbed.tab_style {
tab_style
} else {
tabbed.style
};
let hover_style = if let Some(hover_style) = tabbed.hover_style {
hover_style
} else {
tabbed.style
};
buf.set_style(state.tab_title_area, tabbed.style);
tabbed.block.clone().render(state.block_area, buf);
for (idx, tab_area) in state.tab_title_areas.iter().copied().enumerate() {
if Some(idx) == state.selected() {
buf.set_style(tab_area, tab_style.patch(select_style));
} else {
buf.set_style(tab_area, tab_style);
}
let txt_area = match tabbed.placement {
TabPlacement::Top | TabPlacement::Right | TabPlacement::Bottom => {
tab_area.inner(Margin::new(1, 0))
}
TabPlacement::Left => {
if tabbed.closeable {
Rect::new(
tab_area.x + 3,
tab_area.y,
tab_area.width - 4,
tab_area.height,
)
} else {
tab_area.inner(Margin::new(1, 0))
}
}
};
tabbed.tabs[idx].clone().render(txt_area, buf);
}
if tabbed.closeable {
for i in 0..state.tab_title_close_areas.len() {
if state.mouse.hover.get() == Some(i) {
buf.set_style(state.tab_title_close_areas[i], hover_style);
}
Span::from(" \u{2A2F} ").render(state.tab_title_close_areas[i], buf);
}
}
}
}