use smallvec::{smallvec, SmallVec};
use amethyst_assets::{AssetStorage, Handle, Loader};
use amethyst_audio::SourceHandle;
use amethyst_core::{
ecs::{
prelude::{Entities, Entity, Read, ReadExpect, World, WriteExpect, WriteStorage},
shred::{ResourceId, SystemData},
},
Parent,
};
use amethyst_rendy::{palette::Srgba, rendy::texture::palette::load_from_srgba, Texture};
use crate::{
font::default::get_default_font,
Anchor, FontAsset, FontHandle, Interactable, Selectable, Stretch, UiButton, UiButtonAction,
UiButtonActionRetrigger,
UiButtonActionType::{self, *},
UiImage, UiPlaySoundAction, UiSoundRetrigger, UiText, UiTransform, WidgetId, Widgets,
};
use std::marker::PhantomData;
const DEFAULT_Z: f32 = 1.0;
const DEFAULT_WIDTH: f32 = 128.0;
const DEFAULT_HEIGHT: f32 = 64.0;
const DEFAULT_TAB_ORDER: u32 = 9;
const DEFAULT_BKGD_COLOR: [f32; 4] = [0.82, 0.83, 0.83, 1.0];
const DEFAULT_TXT_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 1.0];
#[derive(SystemData)]
#[allow(missing_debug_implementations)]
pub struct UiButtonBuilderResources<'a, G: PartialEq + Send + Sync + 'static, I: WidgetId = u32> {
font_asset: Read<'a, AssetStorage<FontAsset>>,
texture_asset: Read<'a, AssetStorage<Texture>>,
loader: ReadExpect<'a, Loader>,
entities: Entities<'a>,
image: WriteStorage<'a, Handle<Texture>>,
mouse_reactive: WriteStorage<'a, Interactable>,
parent: WriteStorage<'a, Parent>,
text: WriteStorage<'a, UiText>,
transform: WriteStorage<'a, UiTransform>,
button_widgets: WriteExpect<'a, Widgets<UiButton, I>>,
sound_retrigger: WriteStorage<'a, UiSoundRetrigger>,
button_action_retrigger: WriteStorage<'a, UiButtonActionRetrigger>,
selectables: WriteStorage<'a, Selectable<G>>,
}
#[derive(Debug, Clone)]
pub struct UiButtonBuilder<G, I: WidgetId> {
id: Option<I>,
x: f32,
y: f32,
z: f32,
width: f32,
height: f32,
tab_order: u32,
anchor: Anchor,
stretch: Stretch,
text: String,
text_color: [f32; 4],
font: Option<FontHandle>,
font_size: f32,
image: Option<Handle<Texture>>,
parent: Option<Entity>,
on_click_start_sound: Option<UiPlaySoundAction>,
on_click_stop_sound: Option<UiPlaySoundAction>,
on_hover_sound: Option<UiPlaySoundAction>,
on_click_start: SmallVec<[UiButtonActionType; 2]>,
on_click_stop: SmallVec<[UiButtonActionType; 2]>,
on_hover_start: SmallVec<[UiButtonActionType; 2]>,
on_hover_stop: SmallVec<[UiButtonActionType; 2]>,
_phantom: PhantomData<G>,
}
impl<G, I> Default for UiButtonBuilder<G, I>
where
I: WidgetId,
{
fn default() -> Self {
UiButtonBuilder {
id: None,
x: 0.,
y: 0.,
z: DEFAULT_Z,
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT,
tab_order: DEFAULT_TAB_ORDER,
anchor: Anchor::TopLeft,
stretch: Stretch::NoStretch,
text: "".to_string(),
text_color: DEFAULT_TXT_COLOR,
font: None,
font_size: 32.,
image: None,
parent: None,
on_click_start_sound: None,
on_click_stop_sound: None,
on_hover_sound: None,
on_click_start: smallvec![],
on_click_stop: smallvec![],
on_hover_start: smallvec![],
on_hover_stop: smallvec![],
_phantom: PhantomData,
}
}
}
impl<'a, G: PartialEq + Send + Sync + 'static, I: WidgetId> UiButtonBuilder<G, I> {
pub fn new<S: ToString>(text: S) -> UiButtonBuilder<G, I> {
let mut builder = UiButtonBuilder::default();
builder.text = text.to_string();
builder
}
pub fn with_id(mut self, id: I) -> Self {
self.id = Some(id);
self
}
pub fn with_parent(mut self, parent: Entity) -> Self {
self.parent = Some(parent);
self
}
pub fn with_anchor(mut self, anchor: Anchor) -> Self {
self.anchor = anchor;
self
}
pub fn with_stretch(mut self, stretch: Stretch) -> Self {
self.stretch = stretch;
self
}
pub fn with_text<S>(mut self, text: S) -> Self
where
S: ToString,
{
self.text = text.to_string();
self
}
pub fn with_image(mut self, image: Handle<Texture>) -> Self {
self.image = Some(image);
self
}
pub fn with_font(mut self, font: FontHandle) -> Self {
self.font = Some(font);
self
}
pub fn with_position(mut self, x: f32, y: f32) -> Self {
self.x = x;
self.y = y;
self
}
pub fn with_layer(mut self, z: f32) -> Self {
self.z = z;
self
}
pub fn with_size(mut self, width: f32, height: f32) -> Self {
self.width = width;
self.height = height;
self
}
pub fn with_tab_order(mut self, tab_order: u32) -> Self {
self.tab_order = tab_order;
self
}
pub fn with_font_size(mut self, size: f32) -> Self {
self.font_size = size;
self
}
pub fn with_text_color(mut self, text_color: [f32; 4]) -> Self {
self.text_color = text_color;
self
}
pub fn with_hover_text_color(mut self, text_color: [f32; 4]) -> Self {
self.on_hover_start.push(SetTextColor(text_color));
self.on_hover_stop.push(UnsetTextColor(text_color));
self
}
pub fn with_press_text_color(mut self, text_color: [f32; 4]) -> Self {
self.on_click_start.push(SetTextColor(text_color));
self.on_click_stop.push(UnsetTextColor(text_color));
self
}
pub fn with_hover_image(mut self, image: UiImage) -> Self {
self.on_hover_start.push(SetImage(image.clone()));
self.on_hover_stop.push(UnsetTexture(image));
self
}
pub fn with_press_image(mut self, image: UiImage) -> Self {
self.on_click_start.push(SetImage(image.clone()));
self.on_click_stop.push(UnsetTexture(image));
self
}
pub fn with_hover_sound(mut self, sound: SourceHandle) -> Self {
self.on_hover_sound = Some(UiPlaySoundAction(sound));
self
}
pub fn with_press_sound(mut self, sound: SourceHandle) -> Self {
self.on_click_start_sound = Some(UiPlaySoundAction(sound));
self
}
pub fn with_release_sound(mut self, sound: SourceHandle) -> Self {
self.on_click_stop_sound = Some(UiPlaySoundAction(sound));
self
}
pub fn build(mut self, mut res: UiButtonBuilderResources<'a, G, I>) -> (I, UiButton) {
let image_entity = res.entities.create();
let text_entity = res.entities.create();
let widget = UiButton::new(text_entity, image_entity);
let id = {
let widget = widget.clone();
if let Some(id) = self.id {
let added_id = id.clone();
res.button_widgets.add_with_id(id, widget);
added_id
} else {
res.button_widgets.add(widget)
}
};
if !self.on_click_start.is_empty()
|| !self.on_click_stop.is_empty()
|| !self.on_hover_start.is_empty()
|| !self.on_hover_stop.is_empty()
{
let retrigger = UiButtonActionRetrigger {
on_click_start: actions_with_target(
&mut self.on_click_start.into_iter(),
image_entity,
),
on_click_stop: actions_with_target(
&mut self.on_click_stop.into_iter(),
image_entity,
),
on_hover_start: actions_with_target(
&mut self.on_hover_start.into_iter(),
image_entity,
),
on_hover_stop: actions_with_target(
&mut self.on_hover_stop.into_iter(),
image_entity,
),
};
res.button_action_retrigger
.insert(image_entity, retrigger)
.expect("Unreachable: Inserting newly created entity");
}
if self.on_click_start_sound.is_some()
|| self.on_click_stop_sound.is_some()
|| self.on_hover_sound.is_some()
{
let retrigger = UiSoundRetrigger {
on_click_start: self.on_click_start_sound,
on_click_stop: self.on_click_stop_sound,
on_hover_start: self.on_hover_sound,
on_hover_stop: None,
};
res.sound_retrigger
.insert(image_entity, retrigger)
.expect("Unreachable: Inserting newly created entity");
}
res.transform
.insert(
image_entity,
UiTransform::new(
format!("{}_btn", id),
self.anchor,
Anchor::Middle,
self.x,
self.y,
self.z,
self.width,
self.height,
)
.with_stretch(self.stretch),
)
.expect("Unreachable: Inserting newly created entity");
res.selectables
.insert(image_entity, Selectable::<G>::new(self.tab_order))
.expect("Unreachable: Inserting newly created entity");
let image_handle = self.image.unwrap_or_else(|| {
res.loader.load_from_data(
load_from_srgba(Srgba::new(
DEFAULT_BKGD_COLOR[0],
DEFAULT_BKGD_COLOR[1],
DEFAULT_BKGD_COLOR[2],
DEFAULT_BKGD_COLOR[3],
))
.into(),
(),
&res.texture_asset,
)
});
res.image
.insert(image_entity, image_handle.clone())
.expect("Unreachable: Inserting newly created entity");
res.mouse_reactive
.insert(image_entity, Interactable)
.expect("Unreachable: Inserting newly created entity");
if let Some(parent) = self.parent.take() {
res.parent
.insert(image_entity, Parent { entity: parent })
.expect("Unreachable: Inserting newly created entity");
}
res.transform
.insert(
text_entity,
UiTransform::new(
format!("{}_btn_text", id),
Anchor::Middle,
Anchor::Middle,
0.,
0.,
0.01,
0.,
0.,
)
.into_transparent()
.with_stretch(Stretch::XY {
x_margin: 0.,
y_margin: 0.,
keep_aspect_ratio: false,
}),
)
.expect("Unreachable: Inserting newly created entity");
let font_handle = self
.font
.unwrap_or_else(|| get_default_font(&res.loader, &res.font_asset));
res.text
.insert(
text_entity,
UiText::new(font_handle, self.text, self.text_color, self.font_size),
)
.expect("Unreachable: Inserting newly created entity");
res.parent
.insert(
text_entity,
Parent {
entity: image_entity,
},
)
.expect("Unreachable: Inserting newly created entity");
(id, widget)
}
pub fn build_from_world(self, world: &World) -> (I, UiButton) {
self.build(UiButtonBuilderResources::<G, I>::fetch(&world))
}
}
fn actions_with_target<I>(actions: I, target: Entity) -> Vec<UiButtonAction>
where
I: Iterator<Item = UiButtonActionType>,
{
actions
.map(|action| UiButtonAction {
target,
event_type: action,
})
.collect()
}