use std::marker::PhantomData; use bevy::asset::Assets; use bevy::ecs::system::SystemParam; use bevy::prelude::{ Commands, Component, DespawnRecursiveExt, Entity, In, Query, Res, Resource, With, }; use kayak_font::{Alignment, KayakFont, TextProperties}; use kayak_ui::prelude::{ Event, EventDispatcherContext, KayakRootContext, KayakWidgetContext, WidgetParam, WidgetState, }; use crate::assets::AssetHandles; #[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::ui::utilities::EmptyProps); impl kayak_ui::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: ::kayak_ui::prelude::WidgetName, pub styles: ::kayak_ui::prelude::KStyle, pub computed_styles: ::kayak_ui::prelude::ComputedStyles, } impl std::default::Default for $name { fn default() -> $name { let props = $props::default(); $name { name: kayak_ui::prelude::Widget::get_name(&props), props, styles: ::kayak_ui::prelude::KStyle::default(), computed_styles: ::kayak_ui::prelude::ComputedStyles::default(), } } } }; } #[macro_export] macro_rules! parent_widget { ($props: ident => $name: ident) => { #[derive(bevy::prelude::Bundle)] pub struct $name { pub props: $props, pub name: ::kayak_ui::prelude::WidgetName, pub styles: ::kayak_ui::prelude::KStyle, pub computed_styles: ::kayak_ui::prelude::ComputedStyles, pub children: ::kayak_ui::prelude::KChildren, pub on_event: ::kayak_ui::prelude::OnEvent, } impl std::default::Default for $name { fn default() -> $name { let props = $props::default(); $name { name: kayak_ui::prelude::Widget::get_name(&props), props, styles: ::kayak_ui::prelude::KStyle::default(), computed_styles: ::kayak_ui::prelude::ComputedStyles::default(), children: ::kayak_ui::prelude::KChildren::default(), on_event: ::kayak_ui::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( ::kayak_ui::prelude::Widget::get_name(&$props::default()), ::kayak_ui::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( ::kayak_ui::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( ::kayak_ui::prelude::Widget::get_name(&$props::default()), $crate::ui::utilities::widget_update_with_resource::<$props, $state, $resource>, $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( ::kayak_ui::prelude::Widget::get_name(&$props::default()), | ::bevy::prelude::In((widget_context, entity, previous_entity)): ::bevy::prelude::In<(::kayak_ui::prelude::KayakWidgetContext, ::bevy::prelude::Entity, ::bevy::prelude::Entity)>, $( $name: ::bevy::prelude::Res<$resource> ),+, widget_param: ::kayak_ui::prelude::WidgetParam<$props, $state>, | { widget_param.has_changed(&widget_context, entity, previous_entity) $( || $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; fn remove_root_ui( mut commands: Commands, query: Query<Entity, (With<KayakRootContext>, With<StateUIRoot>)>, ) { for entity in &query { commands.entity(entity).despawn_recursive(); } } pub mod context { use kayak_ui::prelude::{widget_update, EmptyState, KayakRootContext}; use kayak_ui::widgets::KayakWidgetsContextPlugin; use kayak_ui::KayakUIPlugin; use crate::register_widget; use crate::ui::components::*; use crate::ui::sync::{UIStatsData, UITradeData, UITravelInfo}; use crate::ui::widgets::*; use crate::world::EncounterState; 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, DebugInfoProps, EmptyState, render_debug_info ); register_widget!(widget_context, PanelProps, EmptyState, render_panel_widget); register_widget!( widget_context, ButtonWidgetProps, ButtonWidgetState, render_button_widget ); register_widget!( widget_context, ImageButtonWidgetProps, ImageButtonWidgetState, render_image_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 ); register_widget!( widget_context, PanelStatProps, EmptyState, render_panel_stat_widget ); register_widget_with_many_resources!( widget_context, TownMenuPanelProps, TownMenuPanelState, render_town_menu_panel, travelinfo: UITravelInfo, statsinfo: UIStatsData ); register_widget_with_resource!( widget_context, TransitPanelProps, EmptyState, UITravelInfo, render_transit_panel ); register_widget_with_resource!( widget_context, ShopPanelProps, EmptyState, UITradeData, render_shop_panel ); register_widget_with_resource!( widget_context, TavernPanelProps, EmptyState, UITradeData, render_tavern_panel ); register_widget_with_resource!( widget_context, StatsPanelProps, EmptyState, UIStatsData, render_stats_panel ); register_widget_with_resource!( widget_context, EncounterPanelProps, EmptyState, EncounterState, render_encounter_panel ); } } } pub type OnEventParams = In<(EventDispatcherContext, WidgetState, Event, Entity)>; #[macro_export] macro_rules! on_button_click { ($param_type: ty, $func_body: expr) => { ::kayak_ui::prelude::OnEvent::new( move |::bevy::prelude::In(( event_dispatcher_context, _widget_state, event, _entity, )): ::bevy::prelude::In<( ::kayak_ui::prelude::EventDispatcherContext, ::kayak_ui::prelude::WidgetState, ::kayak_ui::prelude::Event, ::bevy::prelude::Entity, )>, params: $param_type| { match event.event_type { ::kayak_ui::prelude::EventType::Click(..) => { $func_body(params); } _ => {} } (event_dispatcher_context, event) }, ) }; } #[derive(SystemParam)] pub struct TextSizer<'w, 's> { pub assets: Res<'w, AssetHandles>, pub fonts: Res<'w, Assets<KayakFont>>, #[system_param(ignore)] _phantom: PhantomData<&'s ()>, } impl<'w, 's> TextSizer<'w, 's> { pub fn measure_text(&self, content: &str, font_size: f32) -> (f32, f32) { let font_data = self .fonts .get(&self.assets.kayak_font("equipment_pro")) .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 + 2.0), }, ) .size() } }