Skip to content
Snippets Groups Projects
button.rs 4.59 KiB
Newer Older
use bevy::prelude::*;
use kayak_ui::prelude::*;
use kayak_ui::widgets::{NinePatch, NinePatchBundle, TextProps, TextWidgetBundle};
use kayak_ui::DEFAULT_FONT;
use num_traits::AsPrimitive;

use crate::assets::AssetHandles;
use crate::parent_widget;
use crate::ui::prelude::{edge_px, px, stretch, val_auto, value};

#[derive(Component, Clone, PartialEq, Default)]
pub struct ButtonWidgetProps {
	pub text: String,
	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,
	}
}

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>,
) -> 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),
					};

					if let Ok(mut state) = params.p1().get_mut(state_entity) {
						match &event.event_type {
							EventType::Hover(..) | 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();
								}
							}
							_ => {}
						}
					}

					(event_dispatcher_context, event)
				},
			);

			let button_height = props.font_size + 2.0 + 24.0; // + 8.0;

			let nine_vals = if props.is_disabled {
				NinePatch {
					handle: assets.image("button_disabled"),
					border: Edge::all(12.0),
				}
			} else if state.is_pressed {
				NinePatch {
					handle: assets.image("button_down"),
					border: Edge::all(12.0),
				}
			} else if state.is_hovered {
				NinePatch {
					handle: assets.image("button_active"),
					border: Edge::all(12.0),
				}
			} else {
				NinePatch {
					handle: assets.image("button_idle"),
					border: Edge::all(12.0),
				}
			};

			let padding = if state.is_pressed {
				StyleProp::Value(Edge::new(
					Units::Pixels(6.0),
					Units::Stretch(1.0),
					Units::Pixels(16.0),
					Units::Stretch(1.0),
				))
			} else {
				StyleProp::Value(Edge::new(
					Units::Pixels(3.0),
					Units::Stretch(1.0),
					Units::Pixels(19.0),
					Units::Stretch(1.0),
				))
			};

			*computed = KStyle {
				render_command: value(RenderCommand::Layout),
				min_height: px(32.0),
				min_width: px(32.0),
				height: px(button_height),
				padding: value(Edge::all(Units::Stretch(0.0))),
				..Default::default()
			}
			.with_style(style)
			.into();

			let ninepatch_styles = KStyle {
				padding,
				..Default::default()
			};

			let text_style = KStyle {
				color: value(Color::BLACK),
				..Default::default()
			};

			rsx! {
				<NinePatchBundle
					on_event={events}
					nine_patch={nine_vals}
					styles={ninepatch_styles}
				>
					<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}
					/>
				</NinePatchBundle>
			}
		}
	}
	true
}