use super::behaviors::MouseBehavior;
use crate::{api::prelude::*, prelude::*, proc_macros::*, theme::prelude::*};
const HEADER_CONTAINER: &str = "header_container";
const HEADER_BAR: &str = "header_bar";
const BODY_CONTAINER: &str = "body_container";
#[derive(Default, AsAny)]
pub struct TabHeaderState {
on_header_mouse_down_callback: Option<Box<dyn 'static + Fn(&mut StatesContext, Mouse) -> bool>>,
on_close_click_callback: Option<Box<dyn 'static + Fn(&mut StatesContext, Point) -> bool>>,
header_bar: Entity,
}
impl State for TabHeaderState {
fn init(&mut self, registry: &mut Registry, ctx: &mut Context) {
self.header_bar = ctx.child(HEADER_BAR).entity();
self.update(registry, ctx);
}
fn update(&mut self, _: &mut Registry, ctx: &mut Context) {
let selected = *ctx.widget().get::<bool>("selected");
let vis = *ctx
.get_widget(self.header_bar)
.get::<Visibility>("visibility");
if selected && vis != Visibility::Visible {
ctx.get_widget(self.header_bar)
.set("visibility", Visibility::Visible);
} else if !selected && vis == Visibility::Visible {
ctx.get_widget(self.header_bar)
.set("visibility", Visibility::Collapsed);
}
}
}
widget!(
TabHeader<TabHeaderState> {
background: Brush,
border_radius: f64,
border_width: Thickness,
border_brush: Brush,
padding: Thickness,
foreground: Brush,
text: String16,
font_size: f64,
font: String,
icon: String,
icon_brush: Brush,
icon_size: f64,
icon_font: String,
selected: bool,
pressed: bool,
spacing: f64,
close_button: Visibility
}
);
impl TabHeader {
pub fn on_header_mouse_down<T: 'static + Fn(&mut StatesContext, Mouse) -> bool>(
mut self,
callback: T,
) -> Self {
self.state.on_header_mouse_down_callback = Some(Box::new(callback));
self
}
pub fn on_close_click<T: 'static + Fn(&mut StatesContext, Point) -> bool>(
mut self,
callback: T,
) -> Self {
self.state.on_close_click_callback = Some(Box::new(callback));
self
}
}
impl Template for TabHeader {
fn template(mut self, id: Entity, ctx: &mut BuildContext) -> Self {
let mut button = Button::new()
.style("button_icon_only")
.icon(material_icons_font::MD_CLOSE)
.visibility(("close_button", id));
if let Some(callback) = self.state.on_close_click_callback.take() {
button = button.on_click(callback);
}
let mut mouse_behavior = MouseBehavior::new().enabled(id).target(id.0).pressed(id);
if let Some(callback) = self.state.on_header_mouse_down_callback.take() {
mouse_behavior = mouse_behavior.on_mouse_down(callback);
}
self.name("TabHeader")
.style("tab_header")
.selected(false)
.height(36)
.min_width(64)
.background(colors::LYNCH_COLOR)
.border_radius(4)
.border_width(0)
.border_brush("transparent")
.padding((16, 0, 16, 0))
.foreground(colors::LINK_WATER_COLOR)
.text("Unnamed Tab")
.font_size(fonts::FONT_SIZE_12)
.font("Roboto-Regular")
.icon("")
.icon_font("MaterialIcons-Regular")
.icon_size(fonts::ICON_FONT_SIZE_12)
.icon_brush(colors::LINK_WATER_COLOR)
.spacing(8)
.close_button(Visibility::Visible)
.child(
mouse_behavior
.child(
Stack::new()
.margin(("padding", id))
.orientation("horizontal")
.child(
TextBlock::new()
.text(id)
.v_align("center")
.font(id)
.font_size(id)
.foreground(id)
.build(ctx),
)
.child(button.v_align("center").build(ctx))
.build(ctx),
)
.child(
Container::new()
.id(HEADER_BAR)
.v_align("start")
.visibility("collapsed")
.style("tab_header_bar")
.build(ctx),
)
.build(ctx),
)
}
fn render_object(&self) -> Box<dyn RenderObject> {
Box::new(RectangleRenderObject)
}
}
enum TabWidgetAction {
SelectByIndex(usize),
SelectByBody(Entity),
Add(String, Entity),
Remove(Entity),
SetCloseButtonVisibility(bool),
}
#[derive(Default, AsAny)]
pub struct TabWidgetState {
actions: Vec<TabWidgetAction>,
header_container: Entity,
body_container: Entity,
tabs: Vec<(Entity, Entity)>,
selected: usize,
close_button_visibility: bool,
}
impl TabWidgetState {
pub fn select_by_index(&mut self, index: usize) {
self.actions.push(TabWidgetAction::SelectByIndex(index));
}
pub fn select_by_body(&mut self, entity: Entity) {
self.actions.push(TabWidgetAction::SelectByBody(entity));
}
pub fn remove_by_body(&mut self, entity: Entity) {
self.actions.push(TabWidgetAction::Remove(entity));
}
pub fn add_tab<T: Into<String>>(&mut self, header: T, body: Entity) {
self.actions.push(TabWidgetAction::Add(header.into(), body));
if self.tabs.is_empty() {
self.select_by_index(0);
}
}
pub fn set_close_button_visibility(&mut self, value: bool) {
self.actions
.push(TabWidgetAction::SetCloseButtonVisibility(value));
}
pub fn get_close_button_visibility(&self) -> bool {
self.close_button_visibility
}
pub fn get_index(&self, tab_body: Entity) -> Option<usize> {
for i in 0..self.tabs.len() {
let (_, body) = self.tabs[i];
if body == tab_body {
return Some(i);
}
}
None
}
fn refresh_selected_tab(&mut self, ctx: &mut Context) {
let tab = self.tabs[self.selected];
ctx.get_widget(tab.0).set("selected", true);
toggle_flag("selected", &mut ctx.get_widget(tab.0));
ctx.get_widget(tab.0).update(false);
ctx.get_widget(tab.1).set("visibility", Visibility::Visible);
}
fn select_by_index_internal(&mut self, ctx: &mut Context, mut index: usize) {
if self.tabs.is_empty() {
return;
}
if index >= self.tabs.len() && index != 0 {
index = self.tabs.len() - 1;
}
if self.selected != index {
let current_tab = self.tabs[self.selected];
let new_tab = self.tabs[index];
ctx.get_widget(current_tab.0).set("selected", false);
toggle_flag("selected", &mut ctx.get_widget(current_tab.0));
ctx.get_widget(current_tab.0).update(true);
ctx.get_widget(current_tab.1)
.set("visibility", Visibility::Hidden);
ctx.get_widget(new_tab.0).set("selected", true);
toggle_flag("selected", &mut ctx.get_widget(new_tab.0));
ctx.get_widget(new_tab.0).update(false);
ctx.get_widget(new_tab.1)
.set("visibility", Visibility::Visible);
self.selected = index;
}
}
fn add_tab_internal(&mut self, ctx: &mut Context, header_text: String, body: Entity) {
let header = self.create_tab_header(ctx, header_text, body);
ctx.get_widget(body).set("visibility", Visibility::Hidden);
ctx.append_child_entity_to(header, self.header_container);
ctx.append_child_entity_to(body, self.body_container);
self.tabs.push((header, body));
if self.tabs.len() == 1 {
self.selected = 0;
self.refresh_selected_tab(ctx);
}
}
fn remove_tab_internal(&mut self, ctx: &mut Context, body: Entity) {
if let Some(index) = self.get_index(body) {
let (header, body) = self.tabs.remove(index);
ctx.remove_child_from(header, self.header_container);
ctx.remove_child_from(body, self.body_container);
if !self.tabs.is_empty() {
if self.selected >= self.tabs.len() {
self.selected = self.tabs.len() - 1;
}
if index <= self.selected {
self.refresh_selected_tab(ctx);
}
}
}
}
fn set_close_button_visibility_internal(&mut self, ctx: &mut Context, value: bool) {
if self.close_button_visibility != value {
self.close_button_visibility = value;
let new_visibility = if self.close_button_visibility {
Visibility::Visible
} else {
Visibility::Collapsed
};
for tab in &self.tabs {
ctx.get_widget(tab.0).set("close_button", new_visibility);
}
}
}
fn create_tab_header(&self, ctx: &mut Context, text: String, body: Entity) -> Entity {
let cloned_entity = ctx.entity;
TabHeader::new()
.close_button(if self.close_button_visibility {
Visibility::Visible
} else {
Visibility::Collapsed
})
.text(String16::from(text))
.on_header_mouse_down(move |states, _| {
states
.get_mut::<TabWidgetState>(cloned_entity)
.select_by_body(body);
true
})
.on_close_click(move |states, _| {
states
.get_mut::<TabWidgetState>(cloned_entity)
.remove_by_body(body);
true
})
.build(&mut ctx.build_context())
}
}
impl State for TabWidgetState {
fn init(&mut self, registry: &mut Registry, ctx: &mut Context) {
self.header_container = ctx.child(HEADER_CONTAINER).entity();
self.body_container = ctx.child(BODY_CONTAINER).entity();
self.close_button_visibility = true;
self.update(registry, ctx);
}
fn update(&mut self, _: &mut Registry, ctx: &mut Context) {
let actions: Vec<TabWidgetAction> = self.actions.drain(..).collect();
for action in actions {
match action {
TabWidgetAction::SelectByIndex(index) => {
self.select_by_index_internal(ctx, index);
}
TabWidgetAction::SelectByBody(body) => {
if let Some(index) = self.get_index(body) {
self.select_by_index_internal(ctx, index)
}
}
TabWidgetAction::Add(header_text, body) => {
self.add_tab_internal(ctx, header_text, body);
}
TabWidgetAction::Remove(body) => {
self.remove_tab_internal(ctx, body);
}
TabWidgetAction::SetCloseButtonVisibility(value) => {
self.set_close_button_visibility_internal(ctx, value);
}
}
}
}
}
widget!(
TabWidget<TabWidgetState> {
spacing: f64,
background: Brush,
border_radius: f64,
border_width: Thickness,
border_brush: Brush,
padding: Thickness
}
);
impl TabWidget {
pub fn close_button(mut self, value: bool) -> Self {
self.state
.actions
.push(TabWidgetAction::SetCloseButtonVisibility(value));
self
}
pub fn tab<T: Into<String>>(mut self, header: T, body: Entity) -> Self {
self.state
.actions
.push(TabWidgetAction::Add(header.into(), body));
self
}
}
impl Template for TabWidget {
fn template(self, id: Entity, ctx: &mut BuildContext) -> Self {
self.name("TabWidget").style("tab_widget").child(
Grid::new()
.rows(Rows::create().push(32).push("*"))
.child(
Stack::new()
.id(HEADER_CONTAINER)
.orientation("horizontal")
.spacing(id)
.build(ctx),
)
.child(
Container::new()
.id(BODY_CONTAINER)
.background(id)
.border_brush(id)
.border_width(id)
.border_radius(id)
.attach(Grid::row(1))
.build(ctx),
)
.build(ctx),
)
}
}