use super::*;
use crate::canvas::*;
use crossterm::event::{KeyCode, KeyModifiers};
use std::rc::Rc;
use uid::IdU64;
pub type TabViewSelectionChangedCallback = dyn Fn(&mut TabView);
pub type TabViewKeyHandler = fn(&mut TabView, event: &mut WindowEvent);
pub struct Tab {
name: String,
content: WindowRef,
id: IdU64<Tab>,
}
impl Tab {
pub fn new<S: ToString>(name: S, content: WindowRef) -> Self {
Self {
name: name.to_string(),
content,
id: IdU64::new(),
}
}
pub fn content(&self) -> &WindowRef {
&self.content
}
}
impl PartialEq for Tab {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
#[derive(Default)]
struct TabViewColors {
fg_highlight: Option<Color>,
bg_highlight: Option<Color>,
}
pub struct TabView {
base: WidgetBase,
tabs: Vec<Tab>,
selected: usize,
enumerate_tabs: bool,
callback: Rc<TabViewSelectionChangedCallback>,
colors: TabViewColors,
key_handler: TabViewKeyHandler,
}
impl Default for TabView {
fn default() -> Self {
Self {
base: WidgetBase::default(),
tabs: Vec::default(),
selected: usize::MIN,
enumerate_tabs: false,
callback: Rc::new(Self::default_callback),
colors: TabViewColors::default(),
key_handler: Self::default_key_handler,
}
}
}
impl ItemCollection<Tab> for TabView {
type Index = usize;
fn add_item(&mut self, item: Tab) {
self.tabs.push(item);
}
fn remove_item(&mut self, item: &Tab) {
let idx = self.tabs.iter().position(|x| x == item);
if let Some(index) = idx {
if self.selected == index {
self.selected = usize::MIN;
self.provoke_changed_property(WindowProperty::Focus);
}
self.tabs.remove(index);
self.provoke_changed_property(WindowProperty::Children);
}
}
fn remove_at(&mut self, index: Self::Index) -> Tab {
self.tabs.remove(index)
}
fn get_item(&mut self, index: Self::Index) -> Option<&Tab> {
self.tabs.get(index)
}
fn clear_items(&mut self) {
self.tabs.clear();
self.selected = usize::MIN;
self.provoke_changed_property(WindowProperty::Children);
}
fn items(&self) -> Self::Index {
self.tabs.len()
}
}
impl TabView {
pub const TAB_SEPERATOR: Grapheme = BorderKind::Solid.line_style()[1];
pub const BRACKET_OPEN: Grapheme = Grapheme::new_unchecked("[", GlyphWidth::Half);
pub const BRACKET_CLOSE: Grapheme = Grapheme::new_unchecked("]", GlyphWidth::Half);
pub const ANGLES_RIGHT: Grapheme = Grapheme::new_unchecked("»", GlyphWidth::Half);
pub const ANGLES_LEFT: Grapheme = Grapheme::new_unchecked("«", GlyphWidth::Half);
pub const MIN_TAB_WIDTH: TSize = 4;
pub fn default_callback(&mut self) {}
pub fn default_key_handler(&mut self, event: &mut WindowEvent) {
if let Event::Key(k) = event.raw() {
if self.base.enabled && k.modifiers.contains(KeyModifiers::CONTROL) {
if k.code == KeyCode::Char('x') {
self.select_previous();
event.handled = true;
}
else if k.code == KeyCode::Char('y') {
self.select_next();
event.handled = true;
}
}
}
}
pub fn set_selection_changed_callback<F: Fn(&mut Self) + 'static>(&mut self, callback: F) {
self.callback = Rc::new(callback);
}
pub fn set_key_handler(&mut self, f: TabViewKeyHandler) {
self.key_handler = f;
}
pub fn select_tab(&mut self, tab: &Tab) {
if let Some(idx) = self.tabs.iter().position(|x| x.id == tab.id)
&& idx != self.selected
{
self.selected = idx;
self.selection_changed();
}
}
pub fn enable_tab_enumeration(&mut self) {
self.enumerate_tabs = true;
}
pub fn disable_tab_enumeration(&mut self) {
self.enumerate_tabs = false;
}
pub fn get_selection(&self) -> Option<&Tab> {
self.tabs.get(self.selected)
}
pub fn set_highlight_fg_color(&mut self, color: Option<Color>) {
self.colors.fg_highlight = color;
}
pub fn set_highlight_bg_color(&mut self, color: Option<Color>) {
self.colors.bg_highlight = color;
}
pub fn select_next(&mut self) {
if self.selected < self.tabs.len() - 1 {
self.selected += 1;
self.selection_changed();
}
}
pub fn select_previous(&mut self) {
if self.selected > 0 {
self.selected -= 1;
self.selection_changed();
}
}
fn selection_changed(&mut self) {
self.provoke_changed_property(WindowProperty::Children);
self.provoke_changed_property(WindowProperty::Focus);
let cb = self.callback.clone();
cb(self);
}
}
impl Window for TabView {
fn children(&mut self, mut builder: SubWindowBuilder) -> SubWindows {
let base_rect = builder.base_rect();
if let Some(selection) = self.get_selection() {
builder
.add_rect(
base_rect
.subrect(0, 1, base_rect.size().x, base_rect.size().y - 1)
.unwrap(),
)
.unwrap();
builder.add_child(selection.content.clone());
}
builder.build().unwrap()
}
fn render(&self, canvas: &mut crate::canvas::Canvas) {
let width = canvas.size().x;
let mut row = canvas.get_row_variable_width(0).unwrap();
let selected = self.tabs.get(self.selected);
let mut size_per_tab = TSize::max(Self::MIN_TAB_WIDTH, width / self.tabs.len() as TSize);
let tabs_per_page = width / size_per_tab - 1;
let skip = self.selected / tabs_per_page as usize * tabs_per_page as usize;
let mut x = 0;
if skip > usize::MIN {
size_per_tab = TSize::max(Self::MIN_TAB_WIDTH, width / self.tabs.len() as TSize);
row.add_grapheme(
Self::ANGLES_LEFT,
self.base.colors.base_fg,
self.base.colors.base_bg,
Style::empty(),
)
.ok();
x += 1;
}
for (n, tab) in self.tabs.iter().skip(skip).enumerate() {
row = row.with_custom_width(x + size_per_tab).unwrap();
row.skip(x);
let fg_color;
let bg_color;
match selected.unwrap() == tab {
true => {
row.set_style(Style::Underline);
fg_color = self.colors.fg_highlight.or(self.base.colors.base_fg);
bg_color = self.colors.bg_highlight.or(self.base.colors.base_bg);
}
false => {
fg_color = self.base.colors.base_fg;
bg_color = self.base.colors.base_bg;
}
}
if width - x <= size_per_tab * 2 {
row.add_grapheme(Self::ANGLES_RIGHT, fg_color, bg_color, Style::None)
.unwrap();
break;
}
if n > 0 {
row.add_grapheme(Self::TAB_SEPERATOR, fg_color, bg_color, Style::None)
.ok();
}
if self.enumerate_tabs {
row.add_grapheme(Self::BRACKET_OPEN, fg_color, bg_color, Style::None)
.ok();
let tab_n = (skip + n + 1).to_string();
row.add_string(
&tab_n,
fg_color,
bg_color,
Style::None,
Some(VariableWidthGlyphRow::DOT3_REPLACEMENT),
)
.ok();
row.add_grapheme(Self::BRACKET_CLOSE, fg_color, bg_color, Style::None)
.ok();
row.add_grapheme(Grapheme::PLACEHOLDER, fg_color, bg_color, Style::None)
.ok();
}
row.add_string(
&tab.name,
fg_color,
bg_color,
Style::None,
Some(VariableWidthGlyphRow::DOT3_REPLACEMENT),
)
.ok();
x += size_per_tab;
}
}
fn handle_event(&mut self, event: &mut WindowEvent) {
(self.key_handler)(self, event);
}
fn focus(&self) -> Option<WindowRef> {
self.get_selection().map(|x| x.content.clone())
}
fn is_enabled(&self) -> bool {
self.base.enabled
}
}
impl HasWindowUID for TabView {
fn uid(&self) -> WindowUID {
self.base.uid
}
}
impl WindowLayout for TabView {
fn border(&self) -> BorderStyle {
self.base.border
}
fn alignment(&self) -> (HorizontalAlignment, VerticalAlignment) {
self.base.alignment
}
fn is_visible(&self) -> bool {
self.base.visibility
}
fn margin(&self) -> Thickness {
self.base.margin
}
}
impl Widget for TabView {
fn set_alignment(&mut self, horizontal: HorizontalAlignment, vertical: VerticalAlignment) {
self.base.alignment = (horizontal, vertical);
self.provoke_changed_property(WindowProperty::Alignment);
}
fn set_visibility(&mut self, visibility: bool) {
self.base.visibility = visibility;
self.provoke_changed_property(WindowProperty::IsVisible);
}
fn set_width(&mut self, width: super::Size) {
self.base.size.x = width;
self.provoke_changed_property(WindowProperty::Size);
}
fn set_height(&mut self, height: super::Size) {
self.base.size.y = height;
}
fn set_margin(&mut self, margin: Thickness) {
self.base.margin = margin;
self.provoke_changed_property(WindowProperty::Margin);
}
fn set_border(&mut self, border: BorderStyle) {
self.base.border = border;
self.provoke_changed_property(WindowProperty::Border);
}
fn set_enabled_state(&mut self, is_enabled: bool) {
self.base.enabled = is_enabled;
}
fn set_width_constraint(&mut self, width: SizeConstraint) {
self.base.constraints.x = width;
self.provoke_changed_property(WindowProperty::Size);
}
fn set_height_constraint(&mut self, height: SizeConstraint) {
self.base.constraints.y = height;
self.provoke_changed_property(WindowProperty::Size);
}
}
impl WidgetColors for TabView {
fn set_disabled_color(&mut self, color: Option<Color>) {
self.base.colors.disabled = color;
}
fn set_base_fg_color(&mut self, color: Option<Color>) {
self.base.colors.base_fg = color;
}
fn set_base_bg_color(&mut self, color: Option<Color>) {
self.base.colors.base_bg = color;
}
}