use std;
use crate::{
Color,
FontSize,
Point,
Rect,
Scalar,
};
use crate::position::Dimensions;
use crate::text;
use crate::utils;
use crate::widget;
#[derive(WidgetCommon_)]
pub struct Tabs<'a> {
#[carbide(common_builder)]
common: widget::CommonBuilder,
tabs: &'a [(widget::Id, &'a str)],
style: Style,
maybe_starting_tab_idx: Option<usize>,
}
pub struct State {
tabs: Vec<Tab>,
maybe_selected_tab_idx: Option<usize>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Tab {
id: widget::Id,
button_id: widget::Id,
}
const TAB_BAR_LABEL_PADDING: f64 = 4.0;
#[derive(Copy, Clone, Debug, Default, PartialEq, WidgetStyle_)]
pub struct Style {
#[carbide(default = "Layout::Horizontal")]
pub layout: Option<Layout>,
#[carbide(default = "None")]
pub bar_thickness: Option<Option<Scalar>>,
#[carbide(default = "theme.label_color")]
pub label_color: Option<Color>,
#[carbide(default = "theme.font_size_medium")]
pub label_font_size: Option<FontSize>,
#[carbide(default = "None")]
pub font_id: Option<Option<text::font::Id>>,
#[carbide(default = "widget::old::canvas::Style::default()")]
pub canvas: Option<widget::old::canvas::Style>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Layout {
Horizontal,
Vertical,
}
impl<'a> Tabs<'a> {
pub fn new(tabs: &'a [(widget::Id, &'a str)]) -> Tabs<'a> {
Tabs {
common: widget::CommonBuilder::default(),
style: Style::default(),
tabs: tabs,
maybe_starting_tab_idx: None,
}
}
pub fn starting_canvas(mut self, canvas_id: widget::Id) -> Self {
let maybe_idx = self.tabs.iter().enumerate()
.find(|&(_, &(id, _))| canvas_id == id)
.map(|(idx, &(_, _))| idx);
self.maybe_starting_tab_idx = maybe_idx;
self
}
pub fn pad(self, pad: Scalar) -> Tabs<'a> {
self.pad_left(pad).pad_right(pad).pad_top(pad).pad_bottom(pad)
}
pub fn layout_horizontally(mut self) -> Self {
self.style.layout = Some(Layout::Horizontal);
self
}
pub fn layout_vertically(mut self) -> Self {
self.style.layout = Some(Layout::Vertical);
self
}
pub fn canvas_style(mut self, style: widget::old::canvas::Style) -> Self {
self.style.canvas = Some(style);
self
}
fn map_canvas_style<F>(mut self, map: F) -> Self
where F: FnOnce(widget::old::canvas::Style) -> widget::old::canvas::Style,
{
self.style.canvas = Some(map({
self.style.canvas.clone()
.unwrap_or_else(widget::old::canvas::Style::default)
}));
self
}
pub fn pad_left(self, pad: Scalar) -> Self {
self.map_canvas_style(|mut style| { style.pad_left = Some(pad); style })
}
pub fn pad_right(self, pad: Scalar) -> Self {
self.map_canvas_style(|mut style| { style.pad_right = Some(pad); style })
}
pub fn pad_bottom(self, pad: Scalar) -> Self {
self.map_canvas_style(|mut style| { style.pad_bottom = Some(pad); style })
}
pub fn pad_top(self, pad: Scalar) -> Self {
self.map_canvas_style(|mut style| { style.pad_top = Some(pad); style })
}
pub fn bar_thickness(mut self, thickness: Scalar) -> Self {
self.style.bar_thickness = Some(Some(thickness));
self
}
builder_methods!{
pub starting_tab_idx { maybe_starting_tab_idx = Some(usize) }
pub label_color { style.label_color = Some(Color) }
pub label_font_size { style.label_font_size = Some(FontSize) }
}
}
fn max_text_width<'a, I>(tabs: I, font_size: FontSize, font: &text::Font) -> Scalar
where I: Iterator<Item=&'a (widget::Id, &'a str)>,
{
tabs.fold(0.0, |max_w, &(_, string)| {
let w = text::line::width(string, font, font_size);
if w > max_w { w } else { max_w }
})
}
fn rel_tab_bar_area(dim: Dimensions,
layout: Layout,
maybe_bar_thickness: Option<Scalar>,
font_size: f64,
max_text_width: f64) -> Rect
{
match layout {
Layout::Horizontal => {
let w = dim[0];
let h = horizontal_tab_bar_h(maybe_bar_thickness, font_size);
let x = 0.0;
let y = dim[1] / 2.0 - h / 2.0;
Rect::from_xy_dim([x, y], [w, h])
},
Layout::Vertical => {
let w = vertical_tab_bar_w(maybe_bar_thickness, max_text_width);
let h = dim[1];
let x = -dim[0] / 2.0 + w / 2.0;
let y = 0.0;
Rect::from_xy_dim([x, y], [w, h])
},
}
}
fn horizontal_tab_bar_h(maybe_bar_thickness: Option<Scalar>, font_size: Scalar) -> Scalar {
maybe_bar_thickness.unwrap_or_else(|| font_size + TAB_BAR_LABEL_PADDING * 2.0)
}
fn vertical_tab_bar_w(maybe_bar_thickness: Option<Scalar>, max_text_width: Scalar) -> Scalar {
maybe_bar_thickness.unwrap_or_else(|| max_text_width + TAB_BAR_LABEL_PADDING * 2.0)
}
fn tab_dim(num_tabs: usize, tab_bar_dim: Dimensions, layout: Layout) -> Dimensions {
let width_multi = 1.0 / num_tabs as Scalar;
match layout {
Layout::Horizontal =>
[width_multi * tab_bar_dim[0], tab_bar_dim[1]],
Layout::Vertical =>
[tab_bar_dim[0], width_multi * tab_bar_dim[1]],
}
}
impl<'a> crate::color::Colorable for Tabs<'a> {
fn color(self, color: Color) -> Self {
self.map_canvas_style(|mut style| {
style.color = Some(color);
style
})
}
}
pub struct TabRects<'a> {
tabs: std::slice::Iter<'a, (widget::Id, &'a str)>,
tab_dim: Dimensions,
next_xy: Point,
xy_step: Point,
}
impl<'a> TabRects<'a> {
pub fn new(tabs: &'a [(widget::Id, &'a str)],
layout: Layout,
rel_tab_bar_rect: Rect) -> Self
{
let num_tabs = tabs.len();
let tab_bar_dim = rel_tab_bar_rect.dim();
let tab_dim = tab_dim(num_tabs, tab_bar_dim, layout);
let unpositioned_tab_rect = Rect::from_xy_dim([0.0, 0.0], tab_dim);
let start_tab_rect = unpositioned_tab_rect.top_left_of(rel_tab_bar_rect);
let start_xy = start_tab_rect.xy();
let xy_step = match layout {
Layout::Horizontal => [tab_dim[0], 0.0],
Layout::Vertical => [0.0, tab_dim[1]],
};
TabRects {
tabs: tabs.iter(),
tab_dim: tab_dim,
next_xy: start_xy,
xy_step: xy_step,
}
}
pub fn next_with_id_and_label(&mut self) -> Option<(Rect, widget::Id, &'a str)> {
let TabRects { ref mut tabs, tab_dim, ref mut next_xy, xy_step } = *self;
tabs.next().map(|&(id, label)| {
let xy = *next_xy;
*next_xy = utils::vec2_add(*next_xy, xy_step);
let rect = Rect::from_xy_dim(xy, tab_dim);
(rect, id, label)
})
}
}