ui/src/elements/button.rs
2023-01-27 13:46:54 +01:00

649 lines
18 KiB
Rust

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<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),
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<Clickable>,
hoverable: Arc<Hoverable>,
selectable: Arc<Selectable>,
framable: Arc<Framable>,
iconizable: IconizableWrapper,
textable: TextableWrapper,
#[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 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()?;
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<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.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<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.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(())
}
}