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

use bevy::asset::Assets;
use bevy::ecs::system::SystemParam;
use bevy::prelude::{
Louis's avatar
Louis committed
	Commands, Component, DespawnRecursiveExt, Entity, In, ParamSet, Query, Res, Resource, With,
Louis's avatar
Louis committed
};
use kayak_font::{Alignment, KayakFont, TextProperties};
use kayak_ui::prelude::{
Louis's avatar
Louis committed
	Event, EventDispatcherContext, EventType, FontMapping, KayakRootContext, KayakWidgetContext,
	OnEvent, WidgetParam, WidgetState,
Louis's avatar
Louis committed
};
Louis's avatar
Louis committed
use micro_musicbox::prelude::MusicBox;
Louis's avatar
Louis committed

use crate::theme::ThemeMapping;
Louis's avatar
Louis committed
use crate::tokens::{THEME_SOUND_BUTTON_CLICK, THEME_SOUND_BUTTON_OVER};
Louis's avatar
Louis committed

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

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

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

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

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

#[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::kayak_ui::prelude::Widget::get_name(&$props::default()),
			$crate::kayak_ui::prelude::widget_update::<$props, $state>,
Louis's avatar
Louis committed
			$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::kayak_ui::prelude::Widget::get_name(&$props::default()),
Louis's avatar
Louis committed
			$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::kayak_ui::prelude::Widget::get_name(&$props::default()),
			$crate::widget_update_with_resource::<$props, $state, $resource>,
Louis's avatar
Louis committed
			$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::kayak_ui::prelude::Widget::get_name(&$props::default()),
			$crate::kayak_ui::prelude::widget_update_with_context::<$props, $state, $context>,
Louis's avatar
Louis committed
			$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::kayak_ui::prelude::Widget::get_name(&$props::default()),
Louis's avatar
Louis committed
			|
				$crate::bevy::prelude::In((widget_context, entity, previous_entity)): $crate::bevy::prelude::In<($crate::kayak_ui::prelude::KayakWidgetContext, $crate::bevy::prelude::Entity, $crate::bevy::prelude::Entity)>,
				$( $name: $crate::bevy::prelude::Res<$resource> ),+,
				widget_param: $crate::kayak_ui::prelude::WidgetParam<$props, $state>,
Louis's avatar
Louis committed
			| {
				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::kayak_ui::prelude::Widget::get_name(&$props::default()),
Louis's avatar
Louis committed
			|
				$crate::bevy::prelude::In((widget_context, entity, previous_entity)): $crate::bevy::prelude::In<($crate::kayak_ui::prelude::KayakWidgetContext, $crate::bevy::prelude::Entity, $crate::bevy::prelude::Entity)>,
				$( $r_name: $crate::bevy::prelude::Res<$resource> ),*,
				$( $c_name: $crate::bevy::prelude::Query<$crate::bevy::prelude::Entity, $crate::bevy::prelude::Changed<$context>> ),*,
				widget_param: $crate::kayak_ui::prelude::WidgetParam<$props, $state>,
Louis's avatar
Louis committed
			| {
				$(
					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((widget_context, entity, previous_entity)): In<(KayakWidgetContext, Entity, Entity)>,
	resource: Res<External>,
	widget_param: WidgetParam<Props, State>,
) -> 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<KayakRootContext>, With<StateUIRoot>)>,
) {
	for entity in &query {
		commands.entity(entity).despawn_recursive();
	}
}

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

Louis's avatar
Louis committed
pub mod context {
	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() -> KayakRootContext {
		let mut widget_context = KayakRootContext::new();
		widget_context.add_plugin(KayakWidgetsContextPlugin);
		widget_context.add_plugin(AdventWidgetsPlugin);
		widget_context
	}

	pub struct AdventWidgetsPlugin;
	impl KayakUIPlugin for AdventWidgetsPlugin {
		fn build(&self, widget_context: &mut KayakRootContext) {
			register_widget!(widget_context, PanelProps, EmptyState, render_panel_widget);
			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 type OnEventParams = In<(EventDispatcherContext, WidgetState, Event, Entity)>;

#[macro_export]
macro_rules! on_button_click {
	($param_type: ty, $func_body: expr) => {
		$crate::kayak_ui::prelude::OnEvent::new(
			move |$crate::bevy::prelude::In((
Louis's avatar
Louis committed
				event_dispatcher_context,
				_widget_state,
				event,
				_entity,
			)): $crate::bevy::prelude::In<(
				$crate::kayak_ui::prelude::EventDispatcherContext,
				$crate::kayak_ui::prelude::WidgetState,
				$crate::kayak_ui::prelude::Event,
				$crate::bevy::prelude::Entity,
			)>,
			      params: $param_type| {
				match event.event_type {
					$crate::kayak_ui::prelude::EventType::Click(..) => {
						$func_body(params);
					}
					_ => {}
				}

				(event_dispatcher_context, event)
			},
		)
	};
}

Louis's avatar
Louis committed
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((event_dispatcher_context, _, mut event, _)): In<(
			EventDispatcherContext,
			WidgetState,
			Event,
			Entity,
		)>,
		      mut params: ParamSet<(Query<&Props>, Query<&mut State>, MusicBox<ThemeMapping>)>| {
			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.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 {
				params.p2().play_ui_sfx(THEME_SOUND_BUTTON_CLICK);
			}
			if should_proing {
				params.p2().play_ui_sfx(THEME_SOUND_BUTTON_OVER);
			}

			(event_dispatcher_context, event)
		},
	)
}

Louis's avatar
Louis committed
#[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,
Louis's avatar
Louis committed
					max_size: (100000.0, font_size + 8.0),
Louis's avatar
Louis committed
				},
			)
			.size()
	}
}