683 lines
19 KiB
Rust
683 lines
19 KiB
Rust
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)).xy();
|
|
frame[1].position = (ortho * vec4(right + offset, bottom + offset, 0.0, 1.0)).xy();
|
|
frame[2].position = (ortho * vec4(right + offset, top - offset, 0.0, 1.0)).xy();
|
|
frame[3].position = (ortho * vec4(right + offset, top - offset, 0.0, 1.0)).xy();
|
|
frame[4].position = (ortho * vec4(left - offset, top - offset, 0.0, 1.0)).xy();
|
|
frame[5].position = (ortho * vec4(left - offset, bottom + offset, 0.0, 1.0)).xy();
|
|
|
|
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)).xy();
|
|
frame[1].position = (ortho * vec4(right + offset, bottom + offset, 0.0, 1.0)).xy();
|
|
frame[2].position = (ortho * vec4(right + offset, top - offset, 0.0, 1.0)).xy();
|
|
frame[3].position = (ortho * vec4(right + offset, top - offset, 0.0, 1.0)).xy();
|
|
frame[4].position = (ortho * vec4(left - offset, top - offset, 0.0, 1.0)).xy();
|
|
frame[5].position = (ortho * vec4(left - offset, bottom + offset, 0.0, 1.0)).xy();
|
|
}
|
|
}
|
|
|
|
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(())
|
|
}
|
|
}
|