Newer
Older
use kayak_font::{KayakFont, TextProperties};
use kayak_ui::widgets::{ElementBundle, NinePatch, NinePatchBundle, TextProps, TextWidgetBundle};
use micro_musicbox::prelude::MusicBox;
use num_traits::AsPrimitive;
use crate::assets::AssetHandles;
use crate::parent_widget;
use crate::ui::components::{IconContent, InsetIconProps, InsetIconWidget};
use crate::ui::prelude::{edge_px, px, stretch, val_auto, value};
#[derive(Component, Clone, PartialEq, Default)]
pub struct ButtonWidgetProps {
pub text: String,
pub left_icon: IconContent,
pub right_icon: IconContent,
pub font_size: f32,
pub is_disabled: bool,
pub is_fixed: bool,
}
impl ButtonWidgetProps {
pub fn text(text: impl ToString, font_size: impl AsPrimitive<f32>) -> Self {
Self {
text: text.to_string(),
font_size: font_size.as_(),
..Default::default()
}
}
}
pub fn button_props(text: impl ToString, is_disabled: bool, is_fixed: bool) -> ButtonWidgetProps {
ButtonWidgetProps {
text: text.to_string(),
is_fixed,
is_disabled,
font_size: 32.0,
left_icon: IconContent::None,
right_icon: IconContent::None,
}
}
impl Widget for ButtonWidgetProps {}
parent_widget!(ButtonWidgetProps => ButtonWidget);
#[derive(Component, PartialEq, Clone, Default)]
pub struct ButtonWidgetState {
pub is_pressed: bool,
pub is_hovered: bool,
}
pub fn render_button_widget(
In((mut widget_context, entity)): In<(KayakWidgetContext, Entity)>,
mut commands: Commands,
state_query: Query<&ButtonWidgetState>,
mut query: Query<(&ButtonWidgetProps, &KChildren, &mut ComputedStyles, &KStyle)>,
assets: Res<AssetHandles>,
fonts: Res<Assets<KayakFont>>,
) -> bool {
if let Ok((props, children, mut computed, style)) = query.get_mut(entity) {
let state_entity =
widget_context.use_state(&mut commands, entity, ButtonWidgetState::default());
let parent_id = Some(entity);
if let Ok(state) = state_query.get(state_entity) {
let events = OnEvent::new(
move |In((event_dispatcher_context, _, mut event, _)): In<(
EventDispatcherContext,
WidgetState,
Event,
Entity,
)>,
mut params: ParamSet<(
Query<&ButtonWidgetProps>,
Query<&mut ButtonWidgetState>,
)>| {
let widget_props = match params.p0().get(entity) {
Ok(p) => p.clone(),
Err(..) => return (event_dispatcher_context, event),
};
let mut should_click = false;
let mut should_proing = false;
if let Ok(mut state) = params.p1().get_mut(state_entity) {
match &event.event_type {
EventType::Hover(..) => {
if !widget_props.is_disabled {
state.is_hovered = true;
}
}
EventType::MouseIn(..) => {
if !widget_props.is_disabled {
state.is_hovered = true;
}
}
EventType::MouseOut(..) => {
state.is_hovered = false;
state.is_pressed = false;
}
EventType::MouseDown(..) => {
if !widget_props.is_disabled {
state.is_pressed = true;
}
}
EventType::MouseUp(..) => {
state.is_pressed = false;
}
EventType::Click(..) => {
if widget_props.is_disabled {
event.prevent_default();
event.stop_propagation();
}
}
_ => {}
}
}
if should_click {
params.p2().play_sfx("ui_ping");
}
if should_proing {
params.p2().play_sfx("ui_confirm");
}
(event_dispatcher_context, event)
},
);
let font_data = fonts.get(&assets.kayak_font("equipment_pro")).unwrap();
let content_measurement = font_data.measure(
props.text.as_str(),
TextProperties {
font_size: props.font_size,
line_height: props.font_size + 2.0,
alignment: Alignment::Start,
tab_size: 4,
max_size: (100000.0, props.font_size + 2.0),
},
);
let edge_padding = 12.0;
let (text_width, text_height) = content_measurement.size();
let button_height = text_height + (edge_padding * 2.0); // + 8.0;
let mut button_width = text_width + (edge_padding * 2.0) + 8.0;
if !props.left_icon.is_none() {
button_width += props.font_size + 23.0;
}
if !props.right_icon.is_none() {
button_width += props.font_size + 23.0;
}
let nine_vals = if props.is_disabled {
NinePatch {
handle: assets.image("button_disabled"),
border: Edge::all(edge_padding),
}
} else if state.is_pressed {
NinePatch {
handle: assets.image("button_down"),
border: Edge::all(edge_padding),
}
} else if state.is_hovered {
NinePatch {
handle: assets.image("button_active"),
border: Edge::all(edge_padding),
}
} else {
NinePatch {
handle: assets.image("button_idle"),
border: Edge::all(edge_padding),
let sizing = if state.is_pressed {
KStyle {
top: px(11.0),
bottom: px(11.0),
KStyle {
top: px(8.0),
bottom: px(14.0),
};
*computed = KStyle {
render_command: value(RenderCommand::Quad),
min_height: px(32.0),
min_width: px(32.0),
height: px(button_height),
..Default::default()
}
width: px(button_width),
padding_bottom: stretch(0.0),
padding_top: stretch(0.0),
padding_left: stretch(0.0),
padding_right: stretch(0.0),
..Default::default()
})
.into();
let ninepatch_styles = KStyle {
layout_type: value(LayoutType::Row),
col_between: px(15.0),
padding_left: stretch(1.0),
padding_right: stretch(1.0),
..Default::default()
};
let text_style = KStyle {
color: value(Color::BLACK),
..Default::default()
}
.with_style(if state.is_pressed {
KStyle {
top: px(6.0),
bottom: px(16.0),
..Default::default()
}
} else {
KStyle {
top: px(3.0),
bottom: px(19.0),
// right: px(4.0),
// left: px(4.0),
..Default::default()
}
});
let icon_wrapper_style = KStyle {
..Default::default()
}
.with_style(if state.is_pressed {
KStyle {
padding_top: px(11.0),
padding_bottom: px(11.0),
// padding_right: px(4.0),
// padding_left: px(4.0),
..Default::default()
}
} else {
KStyle {
padding_top: px(8.0),
padding_bottom: px(14.0),
// padding_right: px(4.0),
// padding_left: px(4.0),
..Default::default()
}
});
rsx! {
<NinePatchBundle
on_event={events}
nine_patch={nine_vals}
styles={ninepatch_styles}
>
{ if !props.left_icon.is_none() {
constructor!(
<ElementBundle styles={icon_wrapper_style.clone()}>
<InsetIconWidget
styles={sizing.clone()}
props={InsetIconProps {
image: props.left_icon.clone(),
size: props.font_size
}}
/>
</ElementBundle>
<TextWidgetBundle
text={TextProps {
content: props.text.clone(),
word_wrap: false,
subpixel: true,
size: props.font_size,
line_height: Some(props.font_size + 2.0),
..Default::default()
}}
styles={text_style}
/>
{ if !props.right_icon.is_none() {
constructor!(
<ElementBundle styles={icon_wrapper_style.clone()}>
<InsetIconWidget
styles={sizing.clone()}
props={InsetIconProps {
image: props.right_icon.clone(),
size: props.font_size
}}
/>
</ElementBundle>