use crate::{ builder::validator::buttoninfo::ButtonInfo, guihandler::gui::iconizable::IconizablePositioning, prelude::*, }; use anyhow::Result; use assetpath::AssetPath; use utilities::prelude::*; use vulkan_rs::prelude::*; use super::{ fill_type::{FillType, InnerFillType}, wrapper::{IconizableWrapper, TextableWrapper}, IconBuilderType, }; use cgmath::{vec2, vec4}; use std::sync::{ atomic::{AtomicBool, Ordering::SeqCst}, Arc, Mutex, }; pub struct ButtonBuilder { icon: Option<Arc<Image>>, margin: u32, text: String, text_color: Color, ratio: f32, text_alignment: TextAlignment, button_select_mode: ButtonSelectMode, #[cfg(feature = "audio")] click_sound: Option<AssetPath>, #[cfg(feature = "audio")] hover_sound: Option<AssetPath>, normal: Option<FillTypeInfo>, selected: Option<FillTypeInfo>, } 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<FillTypeInfo>) -> Self { self.normal = Some(normal.into()); self } pub fn set_selected(mut self, selected: impl Into<FillTypeInfo>) -> Self { self.selected = Some(selected.into()); self } pub fn set_icon(mut self, icon: Option<Arc<Image>>, 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<String>) -> 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<GuiHandler>) -> Result<Arc<Button>> { 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), None, self.margin, )?; let info_icon = IconizableWrapper::new( framable.clone(), None, Some(IconizablePositioning { left: 1.2, right: 0.0, top: 1.2, bottom: 0.0, }), self.margin, )?; let button = Arc::new(Button { framable, clickable, hoverable, selectable, textable, iconizable, info_icon, #[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<Clickable>, hoverable: Arc<Hoverable>, selectable: Arc<Selectable>, framable: Arc<Framable>, iconizable: IconizableWrapper, textable: TextableWrapper, info_icon: IconizableWrapper, #[cfg(feature = "audio")] _click_sound: Option<Arc<Audible>>, #[cfg(feature = "audio")] _hover_sound: Option<Arc<Audible>>, click_executable: Arc<Executable<()>>, select_executable: Arc<Executable<bool>>, on_select_executable: Arc<Executable<bool>>, normal: FillType, selected: FillType, button_state: Mutex<ButtonState>, 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<Self>) -> Result<()> { self.selectable.select() } pub fn set_callback<F>(&self, callback: F) where F: Fn() -> Result<()> + Send + Sync + 'static, { self.click_executable.set_callback(move |_| callback()); } pub fn set_select_callback<F>(&self, callback: F) where F: Fn(bool) -> Result<()> + Send + Sync + 'static, { self.on_select_executable.set_callback(callback); } pub fn set_custom_callback<F>(&self, callback: F) where F: Fn(ControllerButton) -> Result<bool> + 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<Image>) -> 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<Option<Arc<Image>>> { self.iconizable.icon() } pub fn set_info_icon(&self, button: &Arc<Image>) -> Result<()> { self.info_icon.set_icon(button, self.visible()) } pub fn clear_info_icon(&self) -> Result<()> { self.info_icon.clear_icon(self.visible()) } pub fn text(&self) -> Result<Option<String>> { 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<GuiHandler>) -> Result<Arc<Self>> { 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<GuiElement<'a>> { 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()?; self.info_icon.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()?; self.info_icon.update_frame()?; if self.select_mode == ButtonSelectMode::Bigger { self.modify_hovered_vbo()?; } Ok(()) } fn selectable(&self) -> Option<&Arc<Selectable>> { 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.info_icon.set_ui_layer(layer)?; self.normal.set_ui_layer(layer); self.selected.set_ui_layer(layer); Ok(()) } fn position_extent(&self) -> (i32, i32, u32, u32) { self.framable.position() } } 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<Button>) -> Result<()> { // let button_weak = Arc::downgrade(&button); // let hovered_changed = Box::new(move |_| { // if let Some(button) = button_weak.upgrade() { // if !button.clickable.clicked() { // if button.hoverable.hovered() { // button.set_button_state(ButtonState::Selected)?; // } else { // button.set_button_state(ButtonState::Normal)?; // } // } // } // Ok(()) // }); // button // .hoverable // .set_hovered_changed_callback(Some(hovered_changed)) // } fn create_clicked_changed_callback(button: Arc<Button>) { let button_weak = Arc::downgrade(&button); let clicked_changed = Box::new(move || { if let Some(button) = button_weak.upgrade() { if button.clickable.clicked() { button.hoverable.set_hovered(false)?; } else { // if let Some(displayable) = displayable_weak.upgrade() { // displayable.update_frame()?; // } } } Ok(()) }); button .clickable .set_clicked_changed_callback(Some(clicked_changed)); } fn create_selected_changed_callback(button: Arc<Button>) { let button_weak = Arc::downgrade(&button); let selected_changed = move |selected| { if let Some(button) = button_weak.upgrade() { if selected { button.set_button_state(ButtonState::Selected)?; } else { button.set_button_state(ButtonState::Normal)?; } } Ok(()) }; button.select_executable.set_callback(selected_changed); } fn modify_hovered_vbo(&self) -> Result<()> { assert!( self.framable.is_framed(), "button frame needs to be defined before hovering can be calculated" ); let offset = (self.framable.bottom() as i32 - self.framable.top()) as f32 * 0.1; match &self.selected.inner { InnerFillType::Image(displayable) => { let buffer = displayable.buffer(); let mut frame = buffer.map_complete()?; let ortho = self.framable.ortho(); let left = self.framable.left() as f32; let right = self.framable.right() as f32; let top = self.framable.top() as f32; let bottom = self.framable.bottom() as f32; frame[0].position = ortho * vec4(left - offset, bottom + offset, 0.0, 1.0); frame[1].position = ortho * vec4(right + offset, bottom + offset, 0.0, 1.0); frame[2].position = ortho * vec4(right + offset, top - offset, 0.0, 1.0); frame[3].position = ortho * vec4(right + offset, top - offset, 0.0, 1.0); frame[4].position = ortho * vec4(left - offset, top - offset, 0.0, 1.0); frame[5].position = ortho * vec4(left - offset, bottom + offset, 0.0, 1.0); frame[0].texture_coordinates = vec2(0.0, 1.0); frame[1].texture_coordinates = vec2(1.0, 1.0); frame[2].texture_coordinates = vec2(1.0, 0.0); frame[3].texture_coordinates = vec2(1.0, 0.0); frame[4].texture_coordinates = vec2(0.0, 0.0); frame[5].texture_coordinates = vec2(0.0, 1.0); } InnerFillType::Color(colorable) => { let buffer = colorable.buffer(); let mut frame = buffer.map_complete()?; let ortho = self.framable.ortho(); let left = self.framable.left() as f32; let right = self.framable.right() as f32; let top = self.framable.top() as f32; let bottom = self.framable.bottom() as f32; frame[0].position = ortho * vec4(left - offset, bottom + offset, 0.0, 1.0); frame[1].position = ortho * vec4(right + offset, bottom + offset, 0.0, 1.0); frame[2].position = ortho * vec4(right + offset, top - offset, 0.0, 1.0); frame[3].position = ortho * vec4(right + offset, top - offset, 0.0, 1.0); frame[4].position = ortho * vec4(left - offset, top - offset, 0.0, 1.0); frame[5].position = ortho * vec4(left - offset, bottom + offset, 0.0, 1.0); } } Ok(()) } fn disable_base(&self) -> Result<()> { self.framable.delete()?; self.selectable.delete()?; self.hoverable.delete()?; self.clickable.delete()?; self.textable.disable()?; self.iconizable.disable()?; self.info_icon.disable()?; self.normal.disable()?; self.selected.disable()?; Ok(()) } #[inline] fn set_button_state(&self, button_state: ButtonState) -> Result<()> { let mut current_button_state = self.button_state.lock().unwrap(); if button_state == *current_button_state { return Ok(()); } *current_button_state = button_state; match button_state { ButtonState::Normal => { if self.visible() { self.normal.enable()?; } self.selected.disable()?; } ButtonState::Selected => { self.normal.disable()?; if self.visible() { self.selected.enable()?; } } } Ok(()) } }