Skip to content
Snippets Groups Projects
utilities.rs 10.3 KiB
Newer Older
Louis's avatar
Louis committed
use std::marker::PhantomData;

use bevy::asset::Assets;
use bevy::ecs::system::SystemParam;
use bevy::prelude::{
	Commands, Component, DespawnRecursiveExt, DetectChanges, Entity, In, Query, Res, ResMut,
	Resource, With,
};
use kayak_font::{Alignment, KayakFont, TextProperties};
use kayak_ui::prelude::{
	EventDispatcherContext, EventType, FontMapping, KEvent, KayakRootContext, KayakWidgetContext,
	OnEvent, WidgetParam, WidgetState,
};
use micro_musicbox::prelude::MusicBox;

use crate::theme::ThemeMapping;
use crate::tokens::{THEME_SOUND_BUTTON_CLICK, THEME_SOUND_BUTTON_OVER};

#[derive(bevy::prelude::Component, Clone, PartialEq, Default)]
pub struct EmptyProps;

#[macro_export]
macro_rules! empty_props {
	($name: ident) => {
		#[derive(::bevy::prelude::Component, Clone, PartialEq, Default)]
		#[repr(transparent)]
		pub struct $name($crate::EmptyProps);
		impl $crate::prelude::Widget for $name {}
	};
}

#[macro_export]
macro_rules! basic_widget {
	($props: ident => $name: ident) => {
		#[derive(::bevy::prelude::Bundle)]
		pub struct $name {
			pub props: $props,
			pub name: $crate::prelude::WidgetName,
			pub styles: $crate::prelude::KStyle,
			pub computed_styles: $crate::prelude::ComputedStyles,
			pub on_event: $crate::prelude::OnEvent,
		}

		impl std::default::Default for $name {
			fn default() -> $name {
				let props = $props::default();
				$name {
					name: $crate::prelude::Widget::get_name(&props),
					props,
					styles: $crate::prelude::KStyle::default(),
					computed_styles: $crate::prelude::ComputedStyles::default(),
					on_event: $crate::prelude::OnEvent::default(),
				}
			}
		}
	};
}
#[macro_export]
macro_rules! parent_widget {
	($props: ident => $name: ident) => {
		#[derive(::bevy::prelude::Bundle)]
		pub struct $name {
			pub props: $props,
			pub name: $crate::prelude::WidgetName,
			pub styles: $crate::prelude::KStyle,
			pub computed_styles: $crate::prelude::ComputedStyles,
			pub children: $crate::prelude::KChildren,
			pub on_event: $crate::prelude::OnEvent,
		}

		impl std::default::Default for $name {
			fn default() -> $name {
				let props = $props::default();
				$name {
					name: $crate::prelude::Widget::get_name(&props),
					props,
					styles: $crate::prelude::KStyle::default(),
					computed_styles: $crate::prelude::ComputedStyles::default(),
					children: $crate::prelude::KChildren::default(),
					on_event: $crate::prelude::OnEvent::default(),
				}
			}
		}
	};
}

#[macro_export]
macro_rules! register_widget {
	($ctx: expr, $props: ident, $state: ident, $system: ident) => {{
		$ctx.add_widget_data::<$props, $state>();
		$ctx.add_widget_system(
			$crate::prelude::Widget::get_name(&$props::default()),
			$crate::prelude::widget_update::<$props, $state>,
			$system,
		);
	}};
}

#[macro_export]
macro_rules! register_widget_with_update {
	($ctx: expr, $props: ident, $state: ident, $system: ident, $update: expr) => {{
		$ctx.add_widget_data::<$props, $state>();
		$ctx.add_widget_system(
			$crate::prelude::Widget::get_name(&$props::default()),
			$update,
			$system,
		);
	}};
}

#[macro_export]
macro_rules! register_widget_with_resource {
	($ctx: expr, $props: ident, $state: ident, $resource: ident, $system: ident) => {{
		$ctx.add_widget_data::<$props, $state>();
		$ctx.add_widget_system(
			$crate::prelude::Widget::get_name(&$props::default()),
			$crate::prelude::widget_update_with_resource::<$props, $state, $resource>,
			$system,
		);
	}};
}

#[macro_export]
macro_rules! register_widget_with_context {
	($ctx: expr, $props: ident, $state: path, $context: ident, $system: ident) => {{
		$ctx.add_widget_data::<$props, $state>();
		$ctx.add_widget_system(
			$crate::prelude::Widget::get_name(&$props::default()),
			$crate::prelude::widget_update_with_context::<$props, $state, $context>,
			$system,
		);
	}};
}

#[macro_export]
macro_rules! register_widget_with_many_resources {
	($ctx: expr, $props: ident, $state: ident, $system: ident, $($name: ident: $resource: ident),+) => {{
		$ctx.add_widget_data::<$props, $state>();
		$ctx.add_widget_system(
			$crate::prelude::Widget::get_name(&$props::default()),
			|
				::bevy::prelude::In((entity, previous_entity)): ::bevy::prelude::In<(::bevy::prelude::Entity, ::bevy::prelude::Entity)>,
				widget_context: $crate::prelude::KayakWidgetContext,
				$( $name: ::bevy::prelude::Res<$resource> ),+,
				widget_param: $crate::prelude::WidgetParam<$props, $state>,
			| {
				widget_param.has_changed(&widget_context, entity, previous_entity)
					$( || $name.is_changed() )+
			},
			$system,
		);
	}};
}

#[macro_export]
macro_rules! register_complex_widget {
	($ctx: expr, $props: ident, $state: ident, $system: ident, R($($r_name: ident: $resource: ident),*), C($($c_name: ident: $context: ident),*)) => {{
		$ctx.add_widget_data::<$props, $state>();
		$ctx.add_widget_system(
			$crate::prelude::Widget::get_name(&$props::default()),
			|
				::bevy::prelude::In((entity, previous_entity)): ::bevy::prelude::In<(::bevy::prelude::Entity, ::bevy::prelude::Entity)>,
				widget_context: $crate::prelude::KayakWidgetContext,$( $r_name: ::bevy::prelude::Res<$resource> ),*,
				$( $c_name: ::bevy::prelude::Query<::bevy::prelude::Entity, ::bevy::prelude::Changed<$context>> ),*,
				widget_param: $crate::prelude::WidgetParam<$props, $state>,
			| {
				$(
					if let Some(context_entity) = widget_context.get_context_entity::<$context>(entity) {
						if $c_name.contains(context_entity) {
							return true;
						}
					}
				)*

				widget_param.has_changed(&widget_context, entity, previous_entity)
					$( || $r_name.is_changed() )*
			},
			$system,
		);
	}};
}

pub fn widget_update_with_resource<
	Props: PartialEq + Component + Clone,
	State: PartialEq + Component + Clone,
	External: Resource,
>(
	In((entity, previous_entity)): In<(Entity, Entity)>,
	widget_context: Res<KayakWidgetContext>,
	widget_param: WidgetParam<Props, State>,
	resource: Res<External>,
) -> bool {
	widget_param.has_changed(&widget_context, entity, previous_entity) || resource.is_changed()
}

#[derive(Component)]
pub struct StateUIRoot;

pub fn remove_root_ui(mut commands: Commands, query: Query<Entity, With<StateUIRoot>>) {
	for entity in &query {
		commands.entity(entity).despawn_recursive();
	}
}

pub fn remove_tagged_context<T: Component>(
	mut commands: Commands,
	query: Query<Entity, (With<KayakRootContext>, With<T>)>,
) {
	for entity in &query {
		commands.entity(entity).despawn_recursive();
	}
}

pub mod context {
	use bevy::prelude::Entity;
	use kayak_ui::prelude::{EmptyState, KayakRootContext};
	use kayak_ui::widgets::KayakWidgetsContextPlugin;
	use kayak_ui::KayakUIPlugin;

	use crate::components::*;
	use crate::register_widget;

	pub fn create_root_context(cam: Entity) -> KayakRootContext {
		let mut widget_context = KayakRootContext::new(cam);
		widget_context.add_plugin(KayakWidgetsContextPlugin);
		widget_context.add_plugin(CresthollowBaseComponentsPlugin);
		widget_context
	}

	pub struct CresthollowBaseComponentsPlugin;
	impl KayakUIPlugin for CresthollowBaseComponentsPlugin {
		fn build(&self, widget_context: &mut KayakRootContext) {
Louis's avatar
Louis committed
			register_widget!(widget_context, PanelProps, EmptyState, render_panel_widget);
Louis's avatar
Louis committed
			register_widget!(
				widget_context,
				ButtonWidgetProps,
				ButtonWidgetState,
				render_button_widget
			);
			// register_widget!(
			// 	widget_context,
			// 	VDividerWidgetProps,
			// 	EmptyState,
			// 	render_v_divider
			// );
			// register_widget!(
			// 	widget_context,
			// 	HDividerWidgetProps,
			// 	EmptyState,
			// 	render_h_divider
			// );
			// register_widget!(
			// 	widget_context,
			// 	ATextBoxProps,
			// 	ATextBoxState,
			// 	render_text_box_widget
			// );
			register_widget!(
				widget_context,
				InsetIconProps,
				EmptyState,
				render_inset_icon_widget
			);
		}
	}
}

pub trait HasDisabledState {
	fn is_disabled(&self) -> bool;
	fn set_disabled(&mut self, value: bool);
}

pub trait HasPressedState {
	fn is_pressed(&self) -> bool;
	fn set_pressed(&mut self, value: bool);
}

pub trait HasHoveredState {
	fn is_hovered(&self) -> bool;
	fn set_hovered(&mut self, value: bool);
}

pub fn button_logic<Props, State>(entity: Entity, state_entity: Entity) -> OnEvent
where
	Props: HasDisabledState + Component + Clone,
	State: HasHoveredState + HasPressedState + Component,
{
	OnEvent::new(
		move |In(_),
		      mut event: ResMut<KEvent>,
		      props_query: Query<&Props>,
		      mut state_query: Query<&mut State>,
		      mut musicbox: MusicBox<ThemeMapping>| {
			let widget_props = match props_query.get(entity) {
				Ok(v) => v,
				Err(_) => return,
			};

			let mut should_click = false;
			let mut should_proing = false;

			if let Ok(mut state) = state_query.get_mut(state_entity) {
				match &event.event_type {
					EventType::Hover(..) => {
						if !widget_props.is_disabled() {
							state.set_hovered(true);
						}
					}
					EventType::MouseIn(..) => {
						if !widget_props.is_disabled() {
							state.set_hovered(true);
							should_click = true;
						}
					}
					EventType::MouseOut(..) => {
						state.set_hovered(false);
						state.set_pressed(false);
					}
					EventType::MouseDown(..) => {
						if !widget_props.is_disabled() {
							state.set_pressed(true);
						}
					}
					EventType::MouseUp(..) => {
						state.set_pressed(false);
					}
					EventType::Click(..) => {
						if widget_props.is_disabled() {
							event.prevent_default();
							event.stop_propagation();
						} else {
							should_proing = true;
						}
					}
					_ => {}
				}
			}

			if should_click {
				musicbox.play_ui_sfx(THEME_SOUND_BUTTON_CLICK);
			}
			if should_proing {
				musicbox.play_ui_sfx(THEME_SOUND_BUTTON_OVER);
			}
		},
	)
}

#[derive(SystemParam)]
pub struct TextSizer<'w, 's> {
	pub assets: Res<'w, ThemeMapping>,
	pub fonts: Res<'w, Assets<KayakFont>>,
	pub fonts_mapping: Res<'w, FontMapping>,
	#[system_param(ignore)]
	_phantom: PhantomData<&'s ()>,
}

impl<'w, 's> TextSizer<'w, 's> {
	pub fn measure_text(&self, content: &str, font_size: f32, font: impl ToString) -> (f32, f32) {
		let font_name = self.assets.font_names().get(&font.to_string()).unwrap();
		let handle = self.fonts_mapping.get_handle(font_name.clone()).unwrap();
		let font_data = self.fonts.get(&handle).unwrap();

		font_data
			.measure(
				content,
				TextProperties {
					font_size,
					line_height: font_size + 2.0,
					alignment: Alignment::Start,
					tab_size: 4,
					max_size: (100000.0, font_size + 8.0),
				},
			)
			.size()
	}
}