use crate::{builder::validator::buttoninfo::ButtonInfo, prelude::*}; use anyhow::Result; use assetpath::AssetPath; use utilities::prelude::*; use vulkan_rs::prelude::*; use super::{ fill_type::{FillType, InnerFillType}, IconBuilderType, IconizableWrapper, TextableWrapper, }; use cgmath::{vec2, vec4}; use std::sync::{ atomic::{AtomicBool, Ordering::SeqCst}, Arc, Mutex, }; pub struct ButtonBuilder { icon: Option>, margin: u32, text: String, text_color: Color, ratio: f32, text_alignment: TextAlignment, button_select_mode: ButtonSelectMode, #[cfg(feature = "audio")] click_sound: Option, #[cfg(feature = "audio")] hover_sound: Option, normal: Option, selected: Option, } impl ButtonBuilder { pub fn set_select_mode(mut self, select_mode: ButtonSelectMode) -> Self { self.button_select_mode = select_mode; self } pub fn set_normal(mut self, normal: impl Into) -> Self { self.normal = Some(normal.into()); self } pub fn set_selected(mut self, selected: impl Into) -> Self { self.selected = Some(selected.into()); self } pub fn set_icon(mut self, icon: Option>, margin: u32) -> Self { self.icon = icon; self.margin = margin; self } pub fn set_text_ratio(mut self, ratio: f32) -> Self { self.ratio = ratio; self } pub fn set_text_alignment(mut self, text_alignment: TextAlignment) -> Self { self.text_alignment = text_alignment; self } pub fn set_text_color(mut self, text_color: Color) -> Self { self.text_color = text_color; self } pub fn set_text(mut self, text: impl Into) -> Self { self.text = text.into(); self } #[cfg(feature = "audio")] pub fn set_click_sound(mut self, path: AssetPath) -> Self { self.click_sound = Some(path); self } #[cfg(feature = "audio")] pub fn set_hover_sound(mut self, path: AssetPath) -> Self { self.hover_sound = Some(path); self } pub fn build(self, gui_handler: Arc) -> Result> { let framable = Framable::new(gui_handler.clone(), false)?; let normal = FillType::new( framable.clone(), match self.normal { Some(info) => info, None => FillTypeInfo::from(gui_handler.menu_button().clone()), }, )?; let selected = FillType::new( framable.clone(), match self.selected { Some(info) => info, None => FillTypeInfo::from(gui_handler.menu_button_selected().clone()), }, )?; let click_executable = Executable::new(); let select_executable = Executable::new(); let on_select_executable = Executable::new(); #[cfg(feature = "audio")] let click_sound = self .click_sound .map(|path| Audible::new(gui_handler.clone(), path)) .transpose()?; #[cfg(feature = "audio")] let hover_sound = self .hover_sound .map(|path| Audible::new(gui_handler.clone(), path)) .transpose()?; let clickable = Clickable::new(framable.clone(), click_executable.clone()); let selectable = Selectable::new( gui_handler, click_executable.clone(), select_executable.clone(), on_select_executable.clone(), ); let hoverable = Hoverable::new( framable.clone(), select_executable.clone(), on_select_executable.clone(), ); #[cfg(feature = "audio")] if let Some(click_sound) = &click_sound { clickable.set_audible(Some(click_sound.clone())); selectable.set_click_audible(Some(click_sound.clone())); } #[cfg(feature = "audio")] if let Some(hover_sound) = &hover_sound { selectable.set_select_audible(Some(hover_sound.clone())); hoverable.set_audible(Some(hover_sound.clone())); } let textable = TextableWrapper::new( framable.clone(), self.text_color, self.ratio, self.text_alignment, ); if !self.text.is_empty() { textable.set_text(&self.text, false)?; } let iconizable = IconizableWrapper::new( framable.clone(), self.icon.map(IconBuilderType::Image), self.margin, )?; let button = Arc::new(Button { framable, clickable, hoverable, selectable, textable, iconizable, #[cfg(feature = "audio")] _click_sound: click_sound, #[cfg(feature = "audio")] _hover_sound: hover_sound, click_executable, select_executable, on_select_executable, normal, selected, button_state: Mutex::new(ButtonState::Normal), visible: AtomicBool::new(false), select_mode: self.button_select_mode, }); // Button::create_hovered_changed_callback(button.clone())?; Button::create_clicked_changed_callback(button.clone()); Button::create_selected_changed_callback(button.clone()); Ok(button) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum ButtonState { Normal, Selected, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ButtonSelectMode { None, Bigger, } pub struct Button { clickable: Arc, hoverable: Arc, selectable: Arc, framable: Arc, iconizable: IconizableWrapper, textable: TextableWrapper, #[cfg(feature = "audio")] _click_sound: Option>, #[cfg(feature = "audio")] _hover_sound: Option>, click_executable: Arc>, select_executable: Arc>, on_select_executable: Arc>, normal: FillType, selected: FillType, button_state: Mutex, select_mode: ButtonSelectMode, visible: AtomicBool, } impl Button { pub fn builder() -> ButtonBuilder { ButtonBuilder { icon: None, margin: 0, text: String::new(), text_color: Color::Black, ratio: 0.7, text_alignment: TextAlignment::Center, #[cfg(feature = "audio")] click_sound: None, #[cfg(feature = "audio")] hover_sound: None, button_select_mode: ButtonSelectMode::Bigger, normal: None, selected: None, } } pub fn select(self: &Arc) -> Result<()> { self.selectable.select() } pub fn set_callback(&self, callback: F) where F: Fn() -> Result<()> + Send + Sync + 'static, { self.click_executable.set_callback(move |_| callback()); } pub fn set_select_callback(&self, callback: F) where F: Fn(bool) -> Result<()> + Send + Sync + 'static, { self.on_select_executable.set_callback(callback); } pub fn set_custom_callback(&self, callback: F) where F: Fn(ControllerButton) -> Result + Send + Sync + 'static, { self.selectable.set_custom_callback(callback); } pub fn set_text(&self, text: impl ToString) -> Result<()> { self.textable.set_text(text, self.visible()) } pub fn set_icon(&self, icon: &Arc) -> Result<()> { self.iconizable.set_icon(icon, self.visible()) } pub fn set_icon_margon(&self, margin: u32) -> Result<()> { self.iconizable.set_margin(margin) } pub fn clear_icon(&self) -> Result<()> { self.iconizable.clear_icon(self.visible()) } pub fn icon(&self) -> Result>> { self.iconizable.icon() } pub fn text(&self) -> Result> { self.textable.text() } pub fn set_text_color(&self, text_color: Color) -> Result<()> { self.textable.set_text_color(text_color) } pub fn try_from(button_info: &ButtonInfo, gui_handler: &Arc) -> Result> { let mut button_builder = Button::builder().set_select_mode(button_info.select_mode); if let Some(normal) = button_info.normal.clone() { button_builder = button_builder.set_normal(normal); } if let Some(selected) = button_info.selected.clone() { button_builder = button_builder.set_selected(selected); } button_builder = button_builder .set_text(button_info.text.read().unwrap().clone()) .set_text_color(button_info.text_color) .set_text_alignment(button_info.text_alignment); if let Some(ratio) = button_info.text_ratio { button_builder = button_builder.set_text_ratio(ratio); } #[cfg(feature = "audio")] if !button_info.click_sound.is_empty() { button_builder = button_builder.set_click_sound(button_info.click_sound.clone()); } #[cfg(feature = "audio")] if !button_info.hover_sound.is_empty() { button_builder = button_builder.set_hover_sound(button_info.hover_sound.clone()); } button_builder = button_builder.set_icon( button_info .icon .clone() .map(|icon| { Image::from_file(AssetPath::from(( gui_handler.resource_base_path().full_path(), icon, )))? .attach_sampler(Sampler::nearest_sampler().build(gui_handler.device())?) .build(gui_handler.device(), gui_handler.queue()) }) .transpose()?, button_info.margin, ); let button = button_builder.build(gui_handler.clone())?; Ok(button) } } impl GuiElementTraits for Button { fn gridable(&self) -> Option<&dyn Gridable> { Some(self) } fn visibility(&self) -> Option<&dyn Visibility> { Some(self) } fn downcast<'a>(&'a self) -> Option> { Some(GuiElement::Button(self)) } } impl Visibility for Button { fn visible(&self) -> bool { self.visible.load(SeqCst) } fn set_visibility(&self, visibility: bool) -> Result<()> { if visibility != self.visible.load(SeqCst) { self.visible.store(visibility, SeqCst); if visibility { self.framable.add()?; self.selectable.add()?; self.hoverable.add()?; self.clickable.add()?; self.textable.enable()?; self.iconizable.enable()?; match *self.button_state.lock().unwrap() { ButtonState::Normal => self.normal.enable()?, ButtonState::Selected => self.selected.enable()?, } } else { self.disable_base()?; } } Ok(()) } } impl Gridable for Button { fn set_frame( &self, x: i32, y: i32, w: u32, h: u32, vert_align: VerticalAlign, hori_align: HorizontalAlign, ) -> Result<()> { self.framable.set_frame(x, y, w, h, vert_align, hori_align); self.normal.update_frame()?; self.selected.update_frame()?; self.textable.update()?; self.iconizable.update_frame()?; if self.select_mode == ButtonSelectMode::Bigger { self.modify_hovered_vbo()?; } Ok(()) } fn selectable(&self) -> Option<&Arc> { Some(&self.selectable) } fn type_name(&self) -> &str { "Button" } fn set_layer(&self, layer: i32) -> Result<()> { self.clickable.set_ui_layer(layer); self.hoverable.set_ui_layer(layer); self.selectable.set_ui_layer(layer); self.framable.set_ui_layer(layer); self.iconizable.set_ui_layer(layer)?; self.textable.set_ui_layer(layer)?; self.normal.set_ui_layer(layer); self.selected.set_ui_layer(layer); Ok(()) } } impl Drop for Button { fn drop(&mut self) { if self.visible.load(SeqCst) { self.disable_base().unwrap(); } } } // private impl Button { // fn create_hovered_changed_callback(button: Arc