use crate::component::{Component, EventCx, MeasureCx};
use crate::event::Event;
use crate::geom::{Rect, Size};
use crate::layout::Constraint;
use crate::node::Node;
use crate::render::RenderCx;
use crate::style::Style;
use crate::text::Text;
pub struct Tabs {
tabs: Vec<(Text, Node)>,
selected: usize,
rect: Rect,
style: Style,
header_style: Style,
selected_style: Style,
}
impl Tabs {
pub fn new() -> Self {
Self {
tabs: Vec::new(),
selected: 0,
rect: Rect::default(),
style: Style::default(),
header_style: Style::default().bold(),
selected_style: Style::default(),
}
}
pub fn tab(mut self, title: impl Into<Text>, component: impl Component + 'static) -> Self {
self.tabs.push((title.into(), Node::new(component)));
self
}
pub fn style(mut self, style: Style) -> Self {
self.style = style;
self
}
pub fn header_style(mut self, style: Style) -> Self {
self.header_style = style;
self
}
pub fn selected_style(mut self, style: Style) -> Self {
self.selected_style = style;
self
}
pub fn selected_index(&self) -> usize {
self.selected
}
pub fn set_selected(&mut self, index: usize, cx: &mut EventCx) {
if index < self.tabs.len() {
self.selected = index;
cx.invalidate_paint();
}
}
fn content_rect(&self) -> Rect {
let y = self.rect.y.saturating_add(2); Rect {
x: self.rect.x,
y,
width: self.rect.width,
height: self.rect.height.saturating_sub(2),
}
}
}
impl Component for Tabs {
fn render(&self, cx: &mut RenderCx) {
if self.tabs.is_empty() {
return;
}
for (i, (title, _)) in self.tabs.iter().enumerate() {
if i == self.selected {
cx.set_style(self.selected_style.clone());
} else {
cx.set_style(self.header_style.clone());
}
cx.text(format!(" {} ", title.first_text()));
if i < self.tabs.len() - 1 {
cx.set_style(self.header_style.clone());
cx.text("│");
}
}
cx.line("");
cx.set_style(self.style.clone());
let total_width: u16 = self.tabs.iter()
.map(|(t, _)| t.max_width() + 2)
.sum::<u16>()
+ (self.tabs.len().saturating_sub(1)) as u16; cx.text("─".repeat(total_width as usize));
cx.line("");
if let Some((_, child)) = self.tabs.get(self.selected) {
child.render_with_parent(cx.buffer, cx.focused_id, cx.clip_rect, cx.wrap, cx.truncate, cx.align, Some(&cx.style));
}
}
fn measure(&self, constraint: Constraint, _cx: &mut MeasureCx) -> Size {
if self.tabs.is_empty() {
return Size { width: 0, height: 0 };
}
let child_size = self.tabs.get(self.selected)
.map(|(_, child)| child.measure(constraint))
.unwrap_or_default();
Size {
width: constraint.max.width,
height: 2u16.saturating_add(child_size.height), }
}
fn event(&mut self, event: &Event, cx: &mut EventCx) {
if self.tabs.is_empty() {
return;
}
if matches!(event, Event::Focus | Event::Blur | Event::Tick) {
return;
}
if cx.phase() == crate::event::EventPhase::Capture {
return;
}
if let Event::Key(key_event) = event {
match &key_event.key {
crate::event::Key::Left => {
if self.selected > 0 {
self.selected -= 1;
} else {
self.selected = self.tabs.len() - 1;
}
cx.invalidate_paint();
cx.dispatch(crate::event::Command::FocusPrev);
return;
}
crate::event::Key::Right => {
if self.selected + 1 < self.tabs.len() {
self.selected += 1;
} else {
self.selected = 0;
}
cx.invalidate_paint();
cx.dispatch(crate::event::Command::FocusNext);
return;
}
crate::event::Key::Char('q') => {
cx.quit();
return;
}
_ => {}
}
}
if let Event::Key(k) = event {
if k.key == crate::event::Key::Char('c') && k.modifiers.ctrl {
cx.quit();
}
}
}
fn layout(&mut self, rect: Rect, _cx: &mut crate::component::LayoutCx) {
self.rect = rect;
let content = self.content_rect();
if let Some((_, child)) = self.tabs.get_mut(self.selected) {
child.layout(content);
}
}
fn for_each_child(&self, f: &mut dyn FnMut(&Node)) {
if let Some((_, child)) = self.tabs.get(self.selected) {
f(child);
}
}
fn for_each_child_mut(&mut self, f: &mut dyn FnMut(&mut Node)) {
if let Some((_, child)) = self.tabs.get_mut(self.selected) {
f(child);
}
}
fn focusable(&self) -> bool {
false
}
fn style(&self) -> Style {
self.style.clone()
}
}