use crate::{ assets::AssetPath, components::{ Component, ComponentBase, ComponentTrait, RefreshData, button::{self, ComponentButton}, }, event::CallbackDataCommon, i18n::Translation, layout::WidgetPair, widget::{ConstructEssentials, div::WidgetDiv}, }; use std::{ cell::RefCell, rc::{Rc, Weak}, sync::Arc, }; use taffy::{ AlignItems, prelude::{auto, length, percent}, }; pub struct Entry<'a> { pub sprite_src: Option>, pub text: Translation, pub name: &'a str, } pub struct Params<'a> { pub style: taffy::Style, pub entries: Vec>, pub selected_entry_name: &'a str, // default: "" pub on_select: Option, } struct MountedEntry { name: Rc, button: Rc, } pub struct TabSelectEvent { pub name: Rc, } pub type TabSelectCallback = Rc anyhow::Result<()>>; struct State { mounted_entries: Vec, selected_entry_name: Rc, on_select: Option, } struct Data {} pub struct ComponentTabs { base: ComponentBase, data: Rc, state: Rc>, } impl ComponentTrait for ComponentTabs { fn base(&self) -> &ComponentBase { &self.base } fn base_mut(&mut self) -> &mut ComponentBase { &mut self.base } fn refresh(&self, _data: &mut RefreshData) { // nothing to do } } impl State { fn select_entry(&mut self, common: &mut CallbackDataCommon, name: &Rc) { let (color_accent, color_button) = { let def = common.state.globals.defaults(); (def.accent_color, def.button_color) }; for entry in &self.mounted_entries { if *entry.name == **name { entry.button.set_color(common, color_accent); } else { entry.button.set_color(common, color_button); } } self.selected_entry_name = name.clone(); if let Some(on_select) = self.on_select.clone() { let evt = TabSelectEvent { name: name.clone() }; common.alterables.dispatch(Box::new(move |common| { (*on_select)(common, evt)?; Ok(()) })); } } } impl ComponentTabs { pub fn on_select(&self, callback: TabSelectCallback) { self.state.borrow_mut().on_select = Some(callback); } } pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Result<(WidgetPair, Rc)> { let mut style = params.style; // force-override style style.overflow.y = taffy::Overflow::Scroll; style.flex_direction = taffy::FlexDirection::Column; style.flex_wrap = taffy::FlexWrap::NoWrap; style.align_items = Some(AlignItems::Center); style.gap = length(4.0); let (root, _) = ess.layout.add_child(ess.parent, WidgetDiv::create(), style)?; let mut mounted_entries = Vec::::new(); // Mount entries for entry in params.entries { let (_, button) = button::construct( &mut ConstructEssentials { layout: ess.layout, parent: root.id, }, button::Params { text: Some(entry.text), sprite_src: entry.sprite_src, style: taffy::Style { min_size: taffy::Size { width: percent(1.0), height: length(32.0), }, justify_content: Some(taffy::JustifyContent::Start), ..Default::default() }, ..Default::default() }, )?; mounted_entries.push(MountedEntry { name: Rc::from(entry.name), button, }); } let data = Rc::new(Data {}); let state = Rc::new(RefCell::new(State { selected_entry_name: Rc::from(params.selected_entry_name), mounted_entries, on_select: params.on_select, })); // handle button clicks for entry in &state.borrow().mounted_entries { entry.button.on_click({ let entry_name = entry.name.clone(); let state = state.clone(); Rc::new(move |common, _| { state.borrow_mut().select_entry(common, &entry_name); Ok(()) }) }); } let base = ComponentBase { id: root.id, lhandles: Default::default(), }; let tabs = Rc::new(ComponentTabs { base, data, state }); ess.layout.defer_component_refresh(Component(tabs.clone())); Ok((root, tabs)) }