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) { 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 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() } }