More CSS work
This commit is contained in:
+13
-7
@@ -1,20 +1,22 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use bevy::{asset::UntypedHandle, prelude::*};
|
use bevy::{
|
||||||
|
asset::UntypedHandle,
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
use bevy_asset_loader::asset_collection::AssetCollection;
|
use bevy_asset_loader::asset_collection::AssetCollection;
|
||||||
|
use ron_asset_manager::prelude::RonAsset;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use strum_macros::EnumIter;
|
use strum_macros::EnumIter;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState,
|
AppState, fighter::{FighterAttributes, FighterCameraProfile, animation::AnimKind, attack::AttackKind}, game_settings::GameSettings, math::{int::FGi32, vec3::FGVec3}, texture_reference::TextureReference,
|
||||||
fighter::{FighterAttributes, FighterCameraProfile, animation::AnimKind, attack::AttackKind},
|
|
||||||
game_settings::GameSettings,
|
|
||||||
math::{int::FGi32, vec3::FGVec3},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Reflect, EnumIter)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Reflect, EnumIter, Default)]
|
||||||
pub enum FighterId {
|
pub enum FighterId {
|
||||||
|
#[default]
|
||||||
TestFighter,
|
TestFighter,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,11 +31,15 @@ pub struct FighterHurtbox {
|
|||||||
pub radius: FGi32,
|
pub radius: FGi32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Asset, Reflect)]
|
#[derive(Clone, Asset, Deserialize, Reflect, RonAsset)]
|
||||||
pub struct FighterManifest {
|
pub struct FighterManifest {
|
||||||
pub id: FighterId,
|
pub id: FighterId,
|
||||||
pub model_path: String,
|
pub model_path: String,
|
||||||
pub baked_animation_path: String,
|
pub baked_animation_path: String,
|
||||||
|
#[asset]
|
||||||
|
#[dependency]
|
||||||
|
#[reflect(ignore)]
|
||||||
|
pub icon: TextureReference,
|
||||||
pub attributes: FighterAttributes,
|
pub attributes: FighterAttributes,
|
||||||
pub camera: FighterCameraProfile,
|
pub camera: FighterCameraProfile,
|
||||||
pub animations: HashMap<AnimKind, String>,
|
pub animations: HashMap<AnimKind, String>,
|
||||||
|
|||||||
+6
-5
@@ -25,9 +25,9 @@ use bevy::{
|
|||||||
asset::processor::{AssetProcessor, ProcessorState}, prelude::*, settings::SettingsPlugin, tasks::block_on,
|
asset::processor::{AssetProcessor, ProcessorState}, prelude::*, settings::SettingsPlugin, tasks::block_on,
|
||||||
};
|
};
|
||||||
use bevy_asset_loader::prelude::*;
|
use bevy_asset_loader::prelude::*;
|
||||||
|
use ron_asset_manager::RonAssetPlugin;
|
||||||
use bevy_wind_waker_shader::prelude::*;
|
use bevy_wind_waker_shader::prelude::*;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use jackdaw_runtime::EditorCategory;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
camera::MatchCameraPlugin, fighter::manifest::FighterManifest, game_settings::GameSettings,
|
camera::MatchCameraPlugin, fighter::manifest::FighterManifest, game_settings::GameSettings,
|
||||||
@@ -49,6 +49,7 @@ pub mod schedule;
|
|||||||
pub mod scripting;
|
pub mod scripting;
|
||||||
pub mod stage;
|
pub mod stage;
|
||||||
pub mod menus;
|
pub mod menus;
|
||||||
|
pub mod texture_reference;
|
||||||
|
|
||||||
#[derive(States, Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
|
#[derive(States, Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
|
||||||
pub enum AppState {
|
pub enum AppState {
|
||||||
@@ -95,9 +96,9 @@ impl Plugin for GamePlugin {
|
|||||||
scripting::importer::FighterScriptImportPlugin,
|
scripting::importer::FighterScriptImportPlugin,
|
||||||
stage::StagePlugin,
|
stage::StagePlugin,
|
||||||
MatchCameraPlugin,
|
MatchCameraPlugin,
|
||||||
bevy_common_assets::ron::RonAssetPlugin::<FighterManifest>::new(&["fighter.ron"]),
|
RonAssetPlugin::<FighterManifest>::create("fighter.ron"),
|
||||||
bevy_common_assets::ron::RonAssetPlugin::<StageManifest>::new(&["stage.ron"]),
|
RonAssetPlugin::<StageManifest>::create("stage.ron"),
|
||||||
bevy_common_assets::ron::RonAssetPlugin::<GameSettings>::new(&["ron"]),
|
RonAssetPlugin::<GameSettings>::create("ron"),
|
||||||
))
|
))
|
||||||
.add_plugins(menus::MenuPlugin)
|
.add_plugins(menus::MenuPlugin)
|
||||||
.add_loading_state(
|
.add_loading_state(
|
||||||
@@ -158,4 +159,4 @@ fn wait_for_asset_processor(
|
|||||||
{
|
{
|
||||||
next_state.set(AppState::LoadCommonAssets);
|
next_state.set(AppState::LoadCommonAssets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,25 @@
|
|||||||
use bevy::{picking::Pickable, prelude::*, text::FontSourceTemplate};
|
use bevy::{picking::Pickable, prelude::*, text::FontSourceTemplate, ui::widget::ImageNodeSize};
|
||||||
|
|
||||||
use crate::{fighter::manifest::FighterManifest, menus::{button::FGUiButton, style::FGUiButtonType}};
|
use crate::{
|
||||||
|
fighter::{FighterId, manifest::FighterManifest}, menus::{button::FGUiButton, style::FGUiButtonType},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Component, Clone, Default, FromTemplate)]
|
#[derive(Component, Clone, FromTemplate)]
|
||||||
pub struct CharacterBox {
|
pub struct CharacterBox {
|
||||||
fighter_manifest: Handle<FighterManifest>,
|
pub fighter_manifest: Handle<FighterManifest>,
|
||||||
|
pub fighter_id: FighterId
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CharacterBox {
|
impl CharacterBox {
|
||||||
pub fn scene(manifest_handle: Handle<FighterManifest>, manifests: &Res<Assets<FighterManifest>>) -> impl Scene {
|
pub fn scene(
|
||||||
let manifest = manifests.get(&manifest_handle).expect("Fighter manifest should be valid");
|
manifest_handle: Handle<FighterManifest>,
|
||||||
|
manifests: &Res<Assets<FighterManifest>>,
|
||||||
|
) -> impl Scene {
|
||||||
|
let manifest = manifests
|
||||||
|
.get(&manifest_handle)
|
||||||
|
.expect("Fighter manifest should be valid");
|
||||||
|
let fighter_icon = manifest.icon.handle().clone();
|
||||||
|
let fighter_id = manifest.id;
|
||||||
bsn! {
|
bsn! {
|
||||||
Node {
|
Node {
|
||||||
width: px(128),
|
width: px(128),
|
||||||
@@ -17,11 +27,15 @@ impl CharacterBox {
|
|||||||
flex_direction: FlexDirection::Column,
|
flex_direction: FlexDirection::Column,
|
||||||
justify_content: JustifyContent::FlexEnd
|
justify_content: JustifyContent::FlexEnd
|
||||||
}
|
}
|
||||||
|
ImageNode {
|
||||||
|
image: fighter_icon
|
||||||
|
}
|
||||||
FGUiButton {
|
FGUiButton {
|
||||||
button_type: FGUiButtonType::CharacterSelectCharacter
|
button_type: FGUiButtonType::CharacterSelectCharacter
|
||||||
}
|
}
|
||||||
CharacterBox {
|
CharacterBox {
|
||||||
fighter_manifest: manifest_handle
|
fighter_manifest: manifest_handle,
|
||||||
|
fighter_id: fighter_id
|
||||||
}
|
}
|
||||||
BackgroundColor(Color::srgb(1.0, 1.0, 1.0))
|
BackgroundColor(Color::srgb(1.0, 1.0, 1.0))
|
||||||
Children [
|
Children [
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
pub enum CharacterSelectErrors {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
pub struct FighterPortrait {
|
pub struct FighterPortrait {
|
||||||
pub player_slot: usize
|
pub player_slot: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FighterPortrait {
|
impl FighterPortrait {
|
||||||
@@ -15,4 +14,4 @@ impl FighterPortrait {
|
|||||||
BackgroundColor(bevy::color::palettes::css::ORANGE)
|
BackgroundColor(bevy::color::palettes::css::ORANGE)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,53 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::{
|
||||||
|
picking::pointer::{PointerId, PointerLocation},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use bevy_egui::egui::accesskit::TextAlign;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
fighter::manifest::{FighterManifest, FighterManifestRegistry},
|
AppState, fighter::manifest::{FighterManifest, FighterManifestRegistry}, game_settings::CommonAssets, input::{LocalInputAssignments, LocalInputSource}, menus::{
|
||||||
input::LocalInputAssignments,
|
MenuMarker, build_menu_with_props,
|
||||||
menus::{MenuMarker, build_menu_with_props, cursor::FGMenuCursor, spawn_menu, wrap_menu},
|
button::FGUiButton,
|
||||||
|
character_select::character_box::CharacterBox,
|
||||||
|
cursor::{FGMenuCursor, MenuCursorInputSource},
|
||||||
|
errors::MenuErrors::{self, FighterManifestNotFound},
|
||||||
|
match_config::{MenuMatchConfig, MenuMatchPlayerSlot},
|
||||||
|
scaling, spawn_menu,
|
||||||
|
style::{FGUiButtonType, FGUiStyle},
|
||||||
|
wrap_menu,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod character_box;
|
pub mod character_box;
|
||||||
|
pub mod errors;
|
||||||
pub mod fighter_portrait;
|
pub mod fighter_portrait;
|
||||||
|
pub mod token;
|
||||||
|
|
||||||
|
#[derive(Component, Clone, Default)]
|
||||||
|
pub struct StartText;
|
||||||
|
|
||||||
#[derive(Component, FromTemplate)]
|
#[derive(Component, FromTemplate)]
|
||||||
pub struct CharacterSelectScreen {
|
pub struct CharacterSelectScreen {
|
||||||
pub fighter_portait_container: Entity,
|
pub fighter_portait_container: Entity,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Component, FromTemplate)]
|
||||||
|
pub struct StartButtonBanner;
|
||||||
|
|
||||||
pub fn setup_character_select(
|
pub fn setup_character_select(
|
||||||
existing_menu: Option<Single<Entity, With<MenuMarker>>>,
|
existing_menu: Option<Single<Entity, With<MenuMarker>>>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
manifest_registry: Res<FighterManifestRegistry>,
|
manifest_registry: Res<FighterManifestRegistry>,
|
||||||
assignments: Res<LocalInputAssignments>,
|
assignments: Res<LocalInputAssignments>,
|
||||||
manifests: Res<Assets<FighterManifest>>,
|
manifests: Res<Assets<FighterManifest>>,
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
common_assets: Res<CommonAssets>,
|
||||||
mut materials: ResMut<Assets<ColorMaterial>>,
|
styles: Res<Assets<FGUiStyle>>,
|
||||||
) {
|
) {
|
||||||
let character_boxes = manifest_registry
|
let character_boxes = manifest_registry
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_, v)| character_box::CharacterBox::scene(v.clone(), &manifests))
|
.map(|(_, v)| character_box::CharacterBox::scene(v.clone(), &manifests))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Create existing portraits
|
|
||||||
let fighter_portraits = assignments
|
|
||||||
.iter()
|
|
||||||
.map(|(slot, _)| fighter_portrait::FighterPortrait::create(*slot))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
for (slot, _) in assignments.iter() {
|
|
||||||
commands.spawn(FGMenuCursor::create_cursor(*slot, &mut meshes, &mut materials));
|
|
||||||
}
|
|
||||||
|
|
||||||
let menu = bsn! {
|
let menu = bsn! {
|
||||||
// Characters
|
// Characters
|
||||||
CharacterSelectScreen {
|
CharacterSelectScreen {
|
||||||
@@ -47,7 +57,8 @@ pub fn setup_character_select(
|
|||||||
flex_direction: FlexDirection::Column
|
flex_direction: FlexDirection::Column
|
||||||
}
|
}
|
||||||
Children [
|
Children [
|
||||||
(Node {
|
(
|
||||||
|
Node {
|
||||||
flex_direction: FlexDirection::Column,
|
flex_direction: FlexDirection::Column,
|
||||||
width: percent(100),
|
width: percent(100),
|
||||||
height: percent(100),
|
height: percent(100),
|
||||||
@@ -55,7 +66,32 @@ pub fn setup_character_select(
|
|||||||
align_items: AlignItems::Center
|
align_items: AlignItems::Center
|
||||||
}
|
}
|
||||||
Children [
|
Children [
|
||||||
{character_boxes}
|
{character_boxes},
|
||||||
|
(
|
||||||
|
// Start banner thingy
|
||||||
|
StartButtonBanner
|
||||||
|
Node {
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
width: percent(100),
|
||||||
|
}
|
||||||
|
FGUiButton {
|
||||||
|
button_type: FGUiButtonType::CharacterSelectStartBanner
|
||||||
|
}
|
||||||
|
Text::new("Peenids")
|
||||||
|
Visibility::Hidden
|
||||||
|
TextLayout {
|
||||||
|
justify: Justify::Center
|
||||||
|
}
|
||||||
|
TextFont {
|
||||||
|
font_size: FontSize::Px(48.0),
|
||||||
|
}
|
||||||
|
TextColor(Color::WHITE)
|
||||||
|
BackgroundColor(Color::BLACK)
|
||||||
|
GlobalZIndex(500)
|
||||||
|
on(|_ev: On<Pointer<Press>>, mut next_state: ResMut<NextState<AppState>>| next_state.set(AppState::StageSelect))
|
||||||
|
)
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@@ -63,15 +99,14 @@ pub fn setup_character_select(
|
|||||||
Node {
|
Node {
|
||||||
flex_direction: FlexDirection::Row,
|
flex_direction: FlexDirection::Row,
|
||||||
width: percent(100),
|
width: percent(100),
|
||||||
|
height: px(400),
|
||||||
justify_content: JustifyContent::Center
|
justify_content: JustifyContent::Center
|
||||||
}
|
}
|
||||||
Children [
|
|
||||||
{fighter_portraits}
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
commands.init_resource::<MenuMatchConfig>();
|
||||||
spawn_menu(existing_menu, commands, menu);
|
spawn_menu(existing_menu, commands, menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,44 +114,58 @@ pub fn create_portrait(
|
|||||||
player_slot: usize,
|
player_slot: usize,
|
||||||
container: Entity,
|
container: Entity,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
style: &FGUiStyle,
|
||||||
mut materials: ResMut<Assets<ColorMaterial>>,
|
input_source: &LocalInputSource,
|
||||||
) {
|
) {
|
||||||
let entity = commands
|
let entity = commands
|
||||||
.spawn_scene(fighter_portrait::FighterPortrait::create(player_slot))
|
.spawn_scene(fighter_portrait::FighterPortrait::create(player_slot))
|
||||||
.id();
|
.id();
|
||||||
commands.entity(container).add_child(entity);
|
commands.entity(container).add_child(entity);
|
||||||
|
|
||||||
commands.spawn(
|
let cursor = commands
|
||||||
FGMenuCursor::create_cursor(player_slot, &mut meshes, &mut materials)
|
.spawn(FGMenuCursor::create_cursor(
|
||||||
);
|
player_slot,
|
||||||
|
style,
|
||||||
|
input_source,
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
commands.spawn(token::CharacterSelectToken::create(
|
||||||
|
cursor,
|
||||||
|
player_slot,
|
||||||
|
style,
|
||||||
|
));
|
||||||
|
commands.entity(container).add_child(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Event)]
|
#[derive(Event)]
|
||||||
pub struct FighterSlotAssigned {
|
pub struct PlayerSlotAssigned {
|
||||||
pub slot: usize,
|
pub slot: usize,
|
||||||
|
pub input_source: LocalInputSource,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_fighter_slot_addition(
|
pub fn handle_fighter_slot_addition(
|
||||||
addition: On<FighterSlotAssigned>,
|
addition: On<PlayerSlotAssigned>,
|
||||||
query: Single<&CharacterSelectScreen>,
|
query: Single<&CharacterSelectScreen>,
|
||||||
commands: Commands,
|
commands: Commands,
|
||||||
meshes: ResMut<Assets<Mesh>>,
|
common_assets: Res<CommonAssets>,
|
||||||
materials: ResMut<Assets<ColorMaterial>>,
|
styles: Res<Assets<FGUiStyle>>,
|
||||||
) {
|
) {
|
||||||
let slot = addition.slot;
|
let slot = addition.slot;
|
||||||
create_portrait(
|
create_portrait(
|
||||||
slot,
|
slot,
|
||||||
query.fighter_portait_container,
|
query.fighter_portait_container,
|
||||||
commands,
|
commands,
|
||||||
meshes,
|
styles
|
||||||
materials,
|
.get(&common_assets.ui_style)
|
||||||
|
.expect("UI Style should be loaded by now"),
|
||||||
|
&addition.input_source,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn css_input(
|
pub fn css_auto_assign_slots(
|
||||||
menu_inputs: Res<crate::menus::input::MenuInputState>,
|
menu_inputs: Res<crate::menus::input::MenuInputState>,
|
||||||
mut assignments: ResMut<LocalInputAssignments>,
|
mut assignments: ResMut<LocalInputAssignments>,
|
||||||
|
mut match_config: ResMut<MenuMatchConfig>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
) {
|
) {
|
||||||
for (source, input) in &menu_inputs.inputs {
|
for (source, input) in &menu_inputs.inputs {
|
||||||
@@ -124,9 +173,98 @@ pub fn css_input(
|
|||||||
let existed = assignments.iter().any(|(_, assigned)| assigned == source);
|
let existed = assignments.iter().any(|(_, assigned)| assigned == source);
|
||||||
if !existed && let Some(free_idx) = assignments.get_free_player_index() {
|
if !existed && let Some(free_idx) = assignments.get_free_player_index() {
|
||||||
assignments.push((free_idx, *source));
|
assignments.push((free_idx, *source));
|
||||||
|
match_config.players.push(MenuMatchPlayerSlot {
|
||||||
|
slot: free_idx,
|
||||||
|
fighter: None,
|
||||||
|
});
|
||||||
info!("New input assignment");
|
info!("New input assignment");
|
||||||
commands.trigger(FighterSlotAssigned { slot: free_idx });
|
commands.trigger(PlayerSlotAssigned {
|
||||||
|
slot: free_idx,
|
||||||
|
input_source: source.clone(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Event)]
|
||||||
|
pub struct FighterSelectedEvent {
|
||||||
|
slot: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_fighter(
|
||||||
|
event: On<Pointer<Press>>,
|
||||||
|
character_boxes: Query<&CharacterBox>,
|
||||||
|
manifests: Res<Assets<FighterManifest>>,
|
||||||
|
mut match_config: ResMut<MenuMatchConfig>,
|
||||||
|
cursors: Query<(&FGMenuCursor, &PointerId)>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) -> Result {
|
||||||
|
if let Some(character_box) = character_boxes.get(event.event_target()).ok() {
|
||||||
|
let cursor = cursors.iter().find(|(_, id)| **id == event.pointer_id);
|
||||||
|
|
||||||
|
if let Some((cursor, _)) = cursor {
|
||||||
|
if let MenuCursorInputSource::Player(slot) = cursor.input_source {
|
||||||
|
let manifest = manifests
|
||||||
|
.get(&character_box.fighter_manifest)
|
||||||
|
.ok_or(FighterManifestNotFound)?;
|
||||||
|
match_config.set_player_fighter(slot, Some(manifest.id))?;
|
||||||
|
commands.trigger(FighterSelectedEvent { slot });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_start_banner_visibility(
|
||||||
|
_addition: On<FighterSelectedEvent>,
|
||||||
|
banner: Single<Entity, With<StartButtonBanner>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
config: Res<MenuMatchConfig>,
|
||||||
|
) {
|
||||||
|
let match_ready = config.all_players_ready();
|
||||||
|
commands.entity(banner.into_inner()).insert(if match_ready {Visibility::Visible} else {Visibility::Hidden});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn copy_cursor_transform_to_token(
|
||||||
|
tokens: Query<(
|
||||||
|
&token::CharacterSelectToken,
|
||||||
|
&mut UiTransform,
|
||||||
|
&ComputedNode,
|
||||||
|
)>,
|
||||||
|
character_boxes: Query<(&CharacterBox, &ComputedNode, &UiGlobalTransform)>,
|
||||||
|
match_config: Res<MenuMatchConfig>,
|
||||||
|
cursors: Query<&UiGlobalTransform, With<FGMenuCursor>>,
|
||||||
|
cam: Single<&Camera, With<IsDefaultUiCamera>>,
|
||||||
|
ui_scale: Res<UiScale>,
|
||||||
|
) -> Result {
|
||||||
|
let camera = cam.into_inner();
|
||||||
|
for (token, mut trf, computed) in tokens {
|
||||||
|
let fighter = match_config.get_player_fighter(token.slot)?;
|
||||||
|
|
||||||
|
if let Some(fighter_id) = fighter {
|
||||||
|
// Has selected fighter, move the token to the fighter portrait
|
||||||
|
let character_box = character_boxes
|
||||||
|
.iter()
|
||||||
|
.find(|(character_box, _, _)| character_box.fighter_id == fighter_id);
|
||||||
|
if let Some((_, _, box_trf)) = character_box {
|
||||||
|
let offset = computed.size * computed.inverse_scale_factor;
|
||||||
|
let translation =
|
||||||
|
scaling::logical_to_ui_position(box_trf.translation, camera, &ui_scale)
|
||||||
|
- offset * 0.5;
|
||||||
|
trf.translation = Val2::new(px(translation.x), px(translation.y));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Ok(cursor_trf) = cursors.get(token.cursor) {
|
||||||
|
let offset = computed.size * computed.inverse_scale_factor;
|
||||||
|
let translation =
|
||||||
|
scaling::logical_to_ui_position(cursor_trf.translation, camera, &ui_scale)
|
||||||
|
- offset;
|
||||||
|
trf.translation.x = px(translation.x);
|
||||||
|
trf.translation.y = px(translation.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
//! Token that is used to select characters in the CSS
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::menus::style::FGUiStyle;
|
||||||
|
|
||||||
|
#[derive(Component, Clone)]
|
||||||
|
pub struct CharacterSelectToken {
|
||||||
|
pub slot: usize,
|
||||||
|
pub cursor: Entity
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CharacterSelectToken {
|
||||||
|
pub fn create(cursor: Entity, slot: usize, style: &FGUiStyle) -> impl Bundle {
|
||||||
|
(
|
||||||
|
CharacterSelectToken {
|
||||||
|
slot,
|
||||||
|
cursor
|
||||||
|
},
|
||||||
|
Pickable::IGNORE,
|
||||||
|
Node {
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
width: px(64),
|
||||||
|
height: px(64),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
ImageNode {
|
||||||
|
image: style.css_token.handle.handle.clone(),
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+59
-36
@@ -1,11 +1,16 @@
|
|||||||
use bevy::{
|
use bevy::{
|
||||||
asset::uuid::Uuid,
|
asset::uuid::Uuid,
|
||||||
picking::{pointer::{Location, PointerAction, PointerId, PointerInput, PointerLocation}, Pickable},
|
picking::{
|
||||||
|
Pickable,
|
||||||
|
pointer::{Location, PointerAction, PointerId, PointerInput, PointerLocation},
|
||||||
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
window::WindowRef,
|
window::WindowRef,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{input::LocalInputAssignments, menus::input::{MenuInput, MenuInputState}};
|
use crate::{
|
||||||
|
input::{LocalInputAssignments, LocalInputSource}, menus::{input::{MenuInput, MenuInputState}, scaling, style::FGUiStyle},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub enum MenuCursorInputSource {
|
pub enum MenuCursorInputSource {
|
||||||
@@ -24,22 +29,27 @@ pub struct FGMenuCursor {
|
|||||||
impl FGMenuCursor {
|
impl FGMenuCursor {
|
||||||
pub fn create_cursor(
|
pub fn create_cursor(
|
||||||
player_slot: usize,
|
player_slot: usize,
|
||||||
meshes: &mut Assets<Mesh>,
|
style: &FGUiStyle,
|
||||||
materials: &mut Assets<ColorMaterial>,
|
input_source: &LocalInputSource,
|
||||||
) -> impl Bundle {
|
) -> impl Bundle {
|
||||||
let material = materials.add(ColorMaterial::from_color(
|
let pointer_id = if matches!(input_source, LocalInputSource::Keyboard) {PointerId::Mouse} else {PointerId::Custom(Uuid::new_v4())};
|
||||||
bevy::color::palettes::tailwind::ROSE_600,
|
let circle_icon = style.cursor.handle().clone();
|
||||||
));
|
|
||||||
let circle_mesh = meshes.add(Circle::new(25.0));
|
|
||||||
let pointer_id = PointerId::Custom(Uuid::new_v4());
|
|
||||||
(
|
(
|
||||||
FGMenuCursor {
|
FGMenuCursor {
|
||||||
input_source: super::cursor::MenuCursorInputSource::Player(player_slot),
|
input_source: super::cursor::MenuCursorInputSource::Player(player_slot),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
|
Node {
|
||||||
|
width: px(64),
|
||||||
|
height: px(64),
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
ImageNode {
|
||||||
|
image: circle_icon,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
pointer_id,
|
pointer_id,
|
||||||
MeshMaterial2d(material),
|
|
||||||
Mesh2d(circle_mesh),
|
|
||||||
Transform::IDENTITY,
|
Transform::IDENTITY,
|
||||||
GlobalZIndex(1000),
|
GlobalZIndex(1000),
|
||||||
Pickable::IGNORE,
|
Pickable::IGNORE,
|
||||||
@@ -47,13 +57,36 @@ impl FGMenuCursor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The player using the keyboard can input things with the mouse, so we should copy their movement input
|
||||||
|
pub fn copy_mouse_input(
|
||||||
|
mut query: Query<(&FGMenuCursor, &mut PointerLocation, &PointerId)>,
|
||||||
|
assignments: Res<LocalInputAssignments>,
|
||||||
|
mut input_events: MessageReader<Pointer<Move>>
|
||||||
|
) {
|
||||||
|
|
||||||
|
for event in input_events.read() {
|
||||||
|
if let PointerId::Mouse = event.pointer_id {
|
||||||
|
for (cursor, mut location, _) in query.iter_mut() {
|
||||||
|
let is_mouse = match cursor.input_source {
|
||||||
|
MenuCursorInputSource::Any => true,
|
||||||
|
MenuCursorInputSource::Player(slot) => {
|
||||||
|
assignments.get(slot).map(|(_, source)| {
|
||||||
|
matches!(source, LocalInputSource::Keyboard)
|
||||||
|
}).unwrap_or_default()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_mouse {
|
||||||
|
location.location = Some(event.pointer_location.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cursor_input(
|
pub fn cursor_input(
|
||||||
window: Single<(Entity, &Window), With<bevy::window::PrimaryWindow>>,
|
window: Single<(Entity, &Window), With<bevy::window::PrimaryWindow>>,
|
||||||
query: Query<(
|
query: Query<(&mut FGMenuCursor, &PointerLocation, &PointerId)>,
|
||||||
&mut FGMenuCursor,
|
|
||||||
&PointerLocation,
|
|
||||||
&PointerId,
|
|
||||||
)>,
|
|
||||||
assignments: Res<LocalInputAssignments>,
|
assignments: Res<LocalInputAssignments>,
|
||||||
menu_inputs: Res<MenuInputState>,
|
menu_inputs: Res<MenuInputState>,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
@@ -109,12 +142,12 @@ pub fn cursor_input(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.accept != cursor.accepting {
|
if input.accept.is_pressed() != cursor.accepting {
|
||||||
cursor.accepting = input.accept;
|
cursor.accepting = input.accept.is_pressed();
|
||||||
pointer_writer.write(PointerInput {
|
pointer_writer.write(PointerInput {
|
||||||
pointer_id: *pointer_id,
|
pointer_id: *pointer_id,
|
||||||
location: pointer_location,
|
location: pointer_location,
|
||||||
action: if input.accept {
|
action: if input.accept.is_pressed() {
|
||||||
PointerAction::Press(PointerButton::Primary)
|
PointerAction::Press(PointerButton::Primary)
|
||||||
} else {
|
} else {
|
||||||
PointerAction::Release(PointerButton::Primary)
|
PointerAction::Release(PointerButton::Primary)
|
||||||
@@ -125,25 +158,15 @@ pub fn cursor_input(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_cursor_transform(
|
pub fn update_cursor_transform(
|
||||||
camera: Single<(&Camera, &GlobalTransform), With<IsDefaultUiCamera>>,
|
camera: Single<&Camera, With<IsDefaultUiCamera>>,
|
||||||
query: Query<(&mut Transform, &PointerLocation), With<FGMenuCursor>>,
|
query: Query<(&mut UiTransform, &PointerLocation), With<FGMenuCursor>>,
|
||||||
|
ui_scale: Res<UiScale>,
|
||||||
) {
|
) {
|
||||||
let (camera, camera_transform) = camera.into_inner();
|
let camera = camera.into_inner();
|
||||||
|
|
||||||
for (mut trf, location) in query {
|
for (mut ui_trf, location) in query {
|
||||||
let cursor_pos_viewport = camera
|
let ui_pos = scaling::logical_to_ui_position(location.location.as_ref().map(|d| d.position).unwrap_or_default(), camera, &ui_scale);
|
||||||
.viewport_to_world_2d(
|
ui_trf.translation = Val2::new(px(ui_pos.x), px(ui_pos.y));
|
||||||
camera_transform,
|
|
||||||
location
|
|
||||||
.clone()
|
|
||||||
.location
|
|
||||||
.map(|d| d.position)
|
|
||||||
.unwrap_or_default(),
|
|
||||||
)
|
|
||||||
.ok()
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
trf.translation = Vec3::new(cursor_pos_viewport.x, cursor_pos_viewport.y, 0.0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
use bevy::{ecs::query::QueryEntityError, reflect::Reflect};
|
||||||
|
|
||||||
|
use crate::{fighter::FighterId, menus::match_config::MenuMatchConfigErrors};
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug, Reflect)]
|
||||||
|
pub enum MenuErrors {
|
||||||
|
#[error("Fighter manifest not found")]
|
||||||
|
FighterManifestNotFound,
|
||||||
|
#[error("Match config error: {0:?}")]
|
||||||
|
MatchConfigError(#[from] MenuMatchConfigErrors),
|
||||||
|
}
|
||||||
+204
-38
@@ -1,92 +1,237 @@
|
|||||||
use bevy::prelude::*;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::input::{
|
use bevy::{input::gamepad::GamepadInput, prelude::*};
|
||||||
GamepadBinding, LocalInputSource, MenuInputMap, MenuInputMapAction,
|
|
||||||
};
|
use crate::input::{GamepadBinding, LocalInputSource, MenuInputMap, MenuInputMapAction};
|
||||||
|
|
||||||
|
#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy)]
|
||||||
|
pub enum MenuInputActionState {
|
||||||
|
#[default]
|
||||||
|
Released,
|
||||||
|
Pressed,
|
||||||
|
JustReleased,
|
||||||
|
JustPressed,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct MenuJoystickState(HashMap<GamepadInput, f32>);
|
||||||
|
|
||||||
|
impl MenuJoystickState {
|
||||||
|
pub fn get(&self, input: impl Into<GamepadInput>) -> f32 {
|
||||||
|
self.0.get(&input.into()).map(|d| *d).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_gamepad(gamepad: &Gamepad) -> Self {
|
||||||
|
let d = gamepad
|
||||||
|
.analog()
|
||||||
|
.all_axes_and_values()
|
||||||
|
.map(|(axis, val)| (*axis, val))
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
Self(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MenuInputActionState {
|
||||||
|
pub fn is_pressed(&self) -> bool {
|
||||||
|
matches!(self, MenuInputActionState::JustPressed)
|
||||||
|
|| matches!(self, MenuInputActionState::Pressed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_just_pressed(&self) -> bool {
|
||||||
|
matches!(self, MenuInputActionState::JustPressed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn accumulate(&mut self, other: &Self) {
|
||||||
|
if *self < *other {
|
||||||
|
*self = *other;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct MenuInput {
|
pub struct MenuInput {
|
||||||
pub movement: Vec2,
|
pub movement: Vec2,
|
||||||
pub accept: bool,
|
pub movement_digital_up: MenuInputActionState,
|
||||||
pub back: bool,
|
pub movement_digital_down: MenuInputActionState,
|
||||||
pub start: bool,
|
pub movement_digital_left: MenuInputActionState,
|
||||||
|
pub movement_digital_right: MenuInputActionState,
|
||||||
|
pub accept: MenuInputActionState,
|
||||||
|
pub back: MenuInputActionState,
|
||||||
|
pub start: MenuInputActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource, Default)]
|
#[derive(Resource, Default)]
|
||||||
pub struct MenuInputState {
|
pub struct MenuInputState {
|
||||||
pub inputs: Vec<(LocalInputSource, MenuInput)>,
|
pub inputs: Vec<(LocalInputSource, MenuInput)>,
|
||||||
|
pub prev_frame_joystick_state: Vec<(LocalInputSource, MenuJoystickState)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MenuInputState {
|
impl MenuInputState {
|
||||||
pub fn get(&self, source: &LocalInputSource) -> MenuInput {
|
pub fn get(&self, source: &LocalInputSource) -> MenuInput {
|
||||||
self.inputs.iter().find(|(candidate, _)| candidate == source).map(|(_, input)| input.clone()).unwrap_or_default()
|
self.inputs
|
||||||
|
.iter()
|
||||||
|
.find(|(candidate, _)| candidate == source)
|
||||||
|
.map(|(_, input)| input.clone())
|
||||||
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn aggregate(&self) -> MenuInput {
|
pub fn aggregate(&self) -> MenuInput {
|
||||||
self.inputs.iter().fold(MenuInput::default(), |mut result, (_, input)| {
|
self.inputs
|
||||||
result.movement += input.movement;
|
.iter()
|
||||||
result.accept |= input.accept;
|
.fold(MenuInput::default(), |mut result, (_, input)| {
|
||||||
result.back |= input.back;
|
result.accumulate(input);
|
||||||
result.start |= input.start;
|
result
|
||||||
result
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MenuInput {
|
impl MenuInput {
|
||||||
pub fn accumulate(&mut self, other: &Self) {
|
pub fn accumulate(&mut self, other: &Self) {
|
||||||
self.movement += other.movement;
|
self.movement += other.movement;
|
||||||
self.accept |= other.accept;
|
self.accept.accumulate(&other.accept);
|
||||||
self.back |= other.back;
|
self.back.accumulate(&other.back);
|
||||||
self.start |= other.start;
|
self.start.accumulate(&other.start);
|
||||||
|
self.movement_digital_up.accumulate(&other.movement_digital_up);
|
||||||
|
self.movement_digital_down.accumulate(&other.movement_digital_down);
|
||||||
|
self.movement_digital_left.accumulate(&other.movement_digital_left);
|
||||||
|
self.movement_digital_right.accumulate(&other.movement_digital_right);
|
||||||
self.movement = self.movement.clamp_length_max(1.0);
|
self.movement = self.movement.clamp_length_max(1.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keyboard_input(key: &ButtonInput<KeyCode>, map: &MenuInputMap) -> MenuInput {
|
fn keyboard_input(key: &ButtonInput<KeyCode>, map: &MenuInputMap) -> MenuInput {
|
||||||
let mut input = MenuInput::default();
|
let mut input = MenuInput::default();
|
||||||
|
|
||||||
|
let key_to_state = |key_code| {
|
||||||
|
if key.just_pressed(key_code) {
|
||||||
|
MenuInputActionState::JustPressed
|
||||||
|
} else if key.pressed(key_code) {
|
||||||
|
MenuInputActionState::Pressed
|
||||||
|
} else if key.just_released(key_code) {
|
||||||
|
MenuInputActionState::JustReleased
|
||||||
|
} else {
|
||||||
|
MenuInputActionState::Released
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for binding in &map.keyboard {
|
for binding in &map.keyboard {
|
||||||
let pressed = key.pressed(binding.key);
|
|
||||||
match binding.action {
|
match binding.action {
|
||||||
MenuInputMapAction::MovementXDir(sign) => {
|
MenuInputMapAction::MovementXDir(sign) => {
|
||||||
input.movement.x += if pressed { sign as f32 } else { 0.0 };
|
let state = key_to_state(binding.key);
|
||||||
|
input.movement.x += if state.is_pressed() { sign as f32 } else { 0.0 };
|
||||||
|
|
||||||
|
if sign.is_positive() {
|
||||||
|
input.movement_digital_right.accumulate(&state);
|
||||||
|
} else if sign.is_negative() {
|
||||||
|
input.movement_digital_left.accumulate(&state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
MenuInputMapAction::MovementYDir(sign) => {
|
MenuInputMapAction::MovementYDir(sign) => {
|
||||||
input.movement.y += if pressed { sign as f32 } else { 0.0 };
|
let state = key_to_state(binding.key);
|
||||||
|
input.movement.y += if state.is_pressed() { sign as f32 } else { 0.0 };
|
||||||
|
|
||||||
|
if sign.is_positive() {
|
||||||
|
input.movement_digital_up.accumulate(&state);
|
||||||
|
} else if sign.is_negative() {
|
||||||
|
input.movement_digital_down.accumulate(&state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
MenuInputMapAction::Accept => input.accept |= pressed,
|
MenuInputMapAction::Accept => input.accept.accumulate(&key_to_state(binding.key)),
|
||||||
MenuInputMapAction::Back => input.back |= pressed,
|
MenuInputMapAction::Back => input.back.accumulate(&key_to_state(binding.key)),
|
||||||
MenuInputMapAction::Start => input.start |= pressed,
|
MenuInputMapAction::Start => input.start.accumulate(&key_to_state(binding.key)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
input.accept = map.keyboard.iter().any(|binding| matches!(binding.action, MenuInputMapAction::Accept) && key.pressed(binding.key));
|
|
||||||
input.back = map.keyboard.iter().any(|binding| matches!(binding.action, MenuInputMapAction::Back) && key.pressed(binding.key));
|
|
||||||
input.start = map.keyboard.iter().any(|binding| matches!(binding.action, MenuInputMapAction::Start) && key.pressed(binding.key));
|
|
||||||
input.movement = input.movement.normalize_or_zero();
|
input.movement = input.movement.normalize_or_zero();
|
||||||
input
|
input
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gamepad_input(pad: &Gamepad, map: &MenuInputMap, deadzone: f32) -> MenuInput {
|
fn gamepad_input(
|
||||||
|
pad: &Gamepad,
|
||||||
|
map: &MenuInputMap,
|
||||||
|
deadzone: f32,
|
||||||
|
prev_frame: &MenuJoystickState,
|
||||||
|
) -> MenuInput {
|
||||||
let mut input = MenuInput::default();
|
let mut input = MenuInput::default();
|
||||||
|
|
||||||
|
let button_to_state = |button| {
|
||||||
|
if pad.just_pressed(button) {
|
||||||
|
MenuInputActionState::JustPressed
|
||||||
|
} else if pad.pressed(button) {
|
||||||
|
MenuInputActionState::Pressed
|
||||||
|
} else if pad.just_released(button) {
|
||||||
|
MenuInputActionState::JustReleased
|
||||||
|
} else {
|
||||||
|
MenuInputActionState::Released
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for binding in &map.gamepad {
|
for binding in &map.gamepad {
|
||||||
let (value, just_pressed) = match binding.binding {
|
let (value, status) = match binding.binding {
|
||||||
GamepadBinding::Button(button) => (if pad.pressed(button) { 1.0 } else { 0.0 }, pad.just_pressed(button)),
|
GamepadBinding::Button(button) => {
|
||||||
GamepadBinding::Axis(axis, sign) => (pad.get(axis).unwrap_or_default() * sign as f32, false),
|
let state = button_to_state(button);
|
||||||
|
|
||||||
|
(if state.is_pressed() { 1.0 } else { 0.0 }, state)
|
||||||
|
}
|
||||||
|
GamepadBinding::Axis(axis, sign) => {
|
||||||
|
let prev_frame = prev_frame.get(axis);
|
||||||
|
let curr_frame = pad.get(axis).unwrap_or_default();
|
||||||
|
|
||||||
|
let prev_frame_in_deadzone = prev_frame.abs() >= deadzone;
|
||||||
|
let curr_frame_in_deadzone = curr_frame.abs() >= deadzone;
|
||||||
|
|
||||||
|
let status = if prev_frame_in_deadzone && curr_frame_in_deadzone {
|
||||||
|
if prev_frame.signum() != curr_frame.signum() {
|
||||||
|
MenuInputActionState::JustPressed
|
||||||
|
} else {
|
||||||
|
MenuInputActionState::Pressed
|
||||||
|
}
|
||||||
|
} else if curr_frame_in_deadzone && !prev_frame_in_deadzone {
|
||||||
|
MenuInputActionState::JustPressed
|
||||||
|
} else if !curr_frame_in_deadzone && prev_frame_in_deadzone {
|
||||||
|
MenuInputActionState::JustReleased
|
||||||
|
} else {
|
||||||
|
MenuInputActionState::Released
|
||||||
|
};
|
||||||
|
|
||||||
|
(curr_frame * sign as f32, status)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
match binding.action {
|
match binding.action {
|
||||||
MenuInputMapAction::MovementXDir(sign) => {
|
MenuInputMapAction::MovementXDir(sign) => {
|
||||||
input.movement.x += value * sign as f32;
|
input.movement.x += value * sign as f32;
|
||||||
|
|
||||||
|
if sign > 0 {
|
||||||
|
input.movement_digital_right.accumulate(&status);
|
||||||
|
} else {
|
||||||
|
input.movement_digital_left.accumulate(&status);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
MenuInputMapAction::MovementYDir(sign) => {
|
MenuInputMapAction::MovementYDir(sign) => {
|
||||||
input.movement.y += value * sign as f32;
|
input.movement.y += value * sign as f32;
|
||||||
|
if sign > 0 {
|
||||||
|
input.movement_digital_up.accumulate(&status);
|
||||||
|
} else {
|
||||||
|
input.movement_digital_down.accumulate(&status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuInputMapAction::Accept => {
|
||||||
|
input.accept.accumulate(&status);
|
||||||
|
}
|
||||||
|
MenuInputMapAction::Back => {
|
||||||
|
input.accept.accumulate(&status);
|
||||||
|
}
|
||||||
|
MenuInputMapAction::Start => {
|
||||||
|
input.accept.accumulate(&status);
|
||||||
}
|
}
|
||||||
MenuInputMapAction::Accept => { input.accept |= value > 0.5; input.accept |= just_pressed; }
|
|
||||||
MenuInputMapAction::Back => { input.back |= value > 0.5; input.back |= just_pressed; }
|
|
||||||
MenuInputMapAction::Start => { input.start |= value > 0.5; input.start |= just_pressed; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if input.movement.x.abs() < deadzone { input.movement.x = 0.0; }
|
if input.movement.x.abs() < deadzone {
|
||||||
if input.movement.y.abs() < deadzone { input.movement.y = 0.0; }
|
input.movement.x = 0.0;
|
||||||
|
}
|
||||||
|
if input.movement.y.abs() < deadzone {
|
||||||
|
input.movement.y = 0.0;
|
||||||
|
}
|
||||||
input.movement = input.movement.clamp_length_max(1.0);
|
input.movement = input.movement.clamp_length_max(1.0);
|
||||||
input
|
input
|
||||||
}
|
}
|
||||||
@@ -99,8 +244,29 @@ pub fn sample_menu_inputs(
|
|||||||
) {
|
) {
|
||||||
state.inputs.clear();
|
state.inputs.clear();
|
||||||
let deadzone = 0.275;
|
let deadzone = 0.275;
|
||||||
state.inputs.push((LocalInputSource::Keyboard, keyboard_input(&key, &map)));
|
state
|
||||||
|
.inputs
|
||||||
|
.push((LocalInputSource::Keyboard, keyboard_input(&key, &map)));
|
||||||
|
|
||||||
|
let mut prev_joystick_states: Vec<(LocalInputSource, MenuJoystickState)> = vec![];
|
||||||
|
std::mem::swap(&mut prev_joystick_states, &mut state.prev_frame_joystick_state);
|
||||||
|
|
||||||
for (entity, pad) in pads.iter() {
|
for (entity, pad) in pads.iter() {
|
||||||
state.inputs.push((LocalInputSource::Gamepad(entity), gamepad_input(pad, &map, deadzone)));
|
let local_source = LocalInputSource::Gamepad(entity);
|
||||||
|
let prev_frame_inputs = state
|
||||||
|
.prev_frame_joystick_state
|
||||||
|
.iter()
|
||||||
|
.find(|(source, _)| *source == local_source)
|
||||||
|
.map(|(_, gp)| gp.clone())
|
||||||
|
.unwrap_or(MenuJoystickState::default());
|
||||||
|
|
||||||
|
state.inputs.push((
|
||||||
|
local_source,
|
||||||
|
gamepad_input(pad, &map, deadzone, &prev_frame_inputs),
|
||||||
|
));
|
||||||
|
|
||||||
|
state
|
||||||
|
.prev_frame_joystick_state
|
||||||
|
.push((local_source, MenuJoystickState::from_gamepad(pad)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ use bevy::{
|
|||||||
ui::auto_directional_navigation::AutoDirectionalNavigation,
|
ui::auto_directional_navigation::AutoDirectionalNavigation,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{AppState, menus::{MenuMarker, button::menu_button, navigation::NavigationDefaultFocus}};
|
use crate::{
|
||||||
|
AppState,
|
||||||
|
menus::{MenuMarker, button::menu_button, navigation::NavigationDefaultFocus},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(SceneComponent, Default, Clone)]
|
#[derive(SceneComponent, Default, Clone)]
|
||||||
pub struct MainMenu;
|
pub struct MainMenu;
|
||||||
@@ -44,6 +47,10 @@ impl MainMenu {
|
|||||||
on(|_event: On<Pointer<Press>>, mut next_state: ResMut<NextState<AppState>>,| next_state.set(AppState::CharacterSelect))
|
on(|_event: On<Pointer<Press>>, mut next_state: ResMut<NextState<AppState>>,| next_state.set(AppState::CharacterSelect))
|
||||||
NavigationDefaultFocus
|
NavigationDefaultFocus
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
menu_button("Versus2")
|
||||||
|
on(|_event: On<Pointer<Press>>, mut next_state: ResMut<NextState<AppState>>,| next_state.set(AppState::CharacterSelect))
|
||||||
|
),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -51,5 +58,4 @@ impl MainMenu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_main_menu(mut commands: Commands) {
|
pub fn setup_main_menu(mut commands: Commands) {}
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::fighter::FighterId;
|
||||||
|
|
||||||
|
pub struct MenuMatchPlayerSlot {
|
||||||
|
pub slot: usize,
|
||||||
|
pub fighter: Option<FighterId>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
pub struct MenuMatchConfig {
|
||||||
|
pub players: Vec<MenuMatchPlayerSlot>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, Reflect)]
|
||||||
|
pub enum MenuMatchConfigErrors {
|
||||||
|
#[error("Fighter slot {0} was invalid!")]
|
||||||
|
PlayerSlotInvalid(usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MenuMatchConfig {
|
||||||
|
pub fn set_player_fighter(&mut self, player_slot: usize, fighter_id: Option<FighterId>) -> Result<(), MenuMatchConfigErrors> {
|
||||||
|
let slot = self.players.get_mut(player_slot).ok_or(MenuMatchConfigErrors::PlayerSlotInvalid(player_slot))?;
|
||||||
|
slot.fighter = fighter_id;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn get_player_fighter(&self, player_slot: usize) -> Result<Option<FighterId>, MenuMatchConfigErrors> {
|
||||||
|
let slot = self.players.get(player_slot).ok_or(MenuMatchConfigErrors::PlayerSlotInvalid(player_slot))?;
|
||||||
|
Ok(slot.fighter)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn all_players_ready(&self) -> bool {
|
||||||
|
let has_unselected_fighter = self.players.iter().any(|player_slot| player_slot.fighter.is_none());
|
||||||
|
!has_unselected_fighter
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
-5
@@ -7,6 +7,7 @@ use bevy::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
use ron_asset_manager::RonAssetPlugin;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState,
|
AppState,
|
||||||
@@ -21,6 +22,9 @@ pub mod main_menu;
|
|||||||
pub mod navigation;
|
pub mod navigation;
|
||||||
pub mod scaling;
|
pub mod scaling;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
|
pub mod match_config;
|
||||||
|
pub mod errors;
|
||||||
|
pub mod stage_select;
|
||||||
|
|
||||||
#[derive(Component, Clone, Default)]
|
#[derive(Component, Clone, Default)]
|
||||||
pub struct MenuMarker {}
|
pub struct MenuMarker {}
|
||||||
@@ -54,7 +58,7 @@ pub fn wrap_menu(menu: impl Scene) -> impl Scene {
|
|||||||
justify_content: JustifyContent::Center,
|
justify_content: JustifyContent::Center,
|
||||||
column_gap: vmin(5.0),
|
column_gap: vmin(5.0),
|
||||||
flex_direction: FlexDirection::Column,
|
flex_direction: FlexDirection::Column,
|
||||||
padding: UiRect { left: vmin(5.0), right: vmin(5.0), top: vmin(5.0), bottom: vmin(5.0) }
|
//padding: UiRect { left: vmin(5.0), right: vmin(5.0), top: vmin(5.0), bottom: vmin(5.0) }
|
||||||
}
|
}
|
||||||
menu
|
menu
|
||||||
)
|
)
|
||||||
@@ -161,15 +165,21 @@ impl Plugin for MenuPlugin {
|
|||||||
)
|
)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
(character_select::css_input, cursor::update_cursor_transform)
|
(
|
||||||
|
character_select::css_auto_assign_slots,
|
||||||
|
cursor::copy_mouse_input,
|
||||||
|
cursor::update_cursor_transform,
|
||||||
|
character_select::copy_cursor_transform_to_token,
|
||||||
|
)
|
||||||
|
.chain()
|
||||||
.run_if(in_state(AppState::CharacterSelect))
|
.run_if(in_state(AppState::CharacterSelect))
|
||||||
.run_if(any_with_component::<CharacterSelectScreen>),
|
.run_if(any_with_component::<CharacterSelectScreen>),
|
||||||
)
|
)
|
||||||
.add_systems(OnExit(AppState::CharacterSelect), cursor::despawn_cursors)
|
.add_systems(OnExit(AppState::CharacterSelect), cursor::despawn_cursors)
|
||||||
.add_observer(character_select::handle_fighter_slot_addition)
|
.add_observer(character_select::handle_fighter_slot_addition)
|
||||||
.add_plugins(bevy_common_assets::ron::RonAssetPlugin::<FGUiStyle>::new(
|
.add_observer(character_select::select_fighter.run_if(in_state(AppState::CharacterSelect)))
|
||||||
&["stylesheet.ron"],
|
.add_observer(character_select::update_start_banner_visibility.run_if(in_state(AppState::CharacterSelect)))
|
||||||
))
|
.add_plugins(RonAssetPlugin::<FGUiStyle>::create("stylesheet.ron"))
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(
|
(
|
||||||
|
|||||||
+36
-9
@@ -1,6 +1,17 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
use bevy::{camera::NormalizedRenderTarget, input_focus::{InputFocus, InputFocusVisible}, math::CompassOctant, picking::{backend::HitData, pointer::{Location, PointerId}}, prelude::*, ui::auto_directional_navigation::AutoDirectionalNavigator};
|
|
||||||
use super::input::MenuInputState;
|
use super::input::MenuInputState;
|
||||||
|
use bevy::{
|
||||||
|
camera::NormalizedRenderTarget,
|
||||||
|
input_focus::{InputFocus, InputFocusVisible},
|
||||||
|
math::CompassOctant,
|
||||||
|
picking::{
|
||||||
|
backend::HitData,
|
||||||
|
pointer::{Location, PointerId},
|
||||||
|
},
|
||||||
|
prelude::*,
|
||||||
|
ui::auto_directional_navigation::AutoDirectionalNavigator,
|
||||||
|
};
|
||||||
|
use bevy_egui::egui::IntoAtoms;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
// Action state and input handling
|
// Action state and input handling
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
@@ -12,8 +23,7 @@ enum DirectionalNavigationAction {
|
|||||||
Select,
|
Select,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirectionalNavigationAction {
|
impl DirectionalNavigationAction {}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Resource)]
|
#[derive(Default, Resource)]
|
||||||
pub struct UINavigationActionState {
|
pub struct UINavigationActionState {
|
||||||
@@ -37,7 +47,21 @@ pub fn process_inputs(
|
|||||||
) {
|
) {
|
||||||
action_state.pressed_actions.clear();
|
action_state.pressed_actions.clear();
|
||||||
let input = menu_inputs.aggregate();
|
let input = menu_inputs.aggregate();
|
||||||
/*let repeat_press = if has_direction && !initial_press {
|
let has_direction = input.movement.length() != 0.0;
|
||||||
|
|
||||||
|
let mut dir_distances = [
|
||||||
|
(input.movement_digital_up, input.movement.dot(Vec2::Y)),
|
||||||
|
(input.movement_digital_down, input.movement.dot(Vec2::NEG_Y)),
|
||||||
|
(input.movement_digital_left, input.movement.dot(Vec2::NEG_X)),
|
||||||
|
(input.movement_digital_right, input.movement.dot(Vec2::X)),
|
||||||
|
];
|
||||||
|
|
||||||
|
let dir_distances_slice = dir_distances.as_mut_slice();
|
||||||
|
dir_distances_slice.sort_by(|a, b| a.1.total_cmp(&b.1));
|
||||||
|
|
||||||
|
let (curr_dir, _) = dir_distances_slice.last().copied().unwrap();
|
||||||
|
|
||||||
|
let repeat_press = if has_direction && !curr_dir.is_just_pressed() {
|
||||||
repeat.held_for += time.delta_secs();
|
repeat.held_for += time.delta_secs();
|
||||||
if !repeat.repeating && repeat.held_for >= 0.3 {
|
if !repeat.repeating && repeat.held_for >= 0.3 {
|
||||||
repeat.repeating = true;
|
repeat.repeating = true;
|
||||||
@@ -51,19 +75,19 @@ pub fn process_inputs(
|
|||||||
if !has_direction {
|
if !has_direction {
|
||||||
repeat.held_for = 0.0;
|
repeat.held_for = 0.0;
|
||||||
repeat.repeating = false;
|
repeat.repeating = false;
|
||||||
} else if initial_press {
|
} else if curr_dir.is_just_pressed() {
|
||||||
repeat.held_for = 0.0;
|
repeat.held_for = 0.0;
|
||||||
repeat.repeating = false;
|
repeat.repeating = false;
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
if initial_press || repeat_press {
|
if curr_dir.is_just_pressed() || repeat_press {
|
||||||
if input.movement.y > 0.0 { action_state.pressed_actions.insert(DirectionalNavigationAction::Up); }
|
if input.movement.y > 0.0 { action_state.pressed_actions.insert(DirectionalNavigationAction::Up); }
|
||||||
if input.movement.y < 0.0 { action_state.pressed_actions.insert(DirectionalNavigationAction::Down); }
|
if input.movement.y < 0.0 { action_state.pressed_actions.insert(DirectionalNavigationAction::Down); }
|
||||||
if input.movement.x < 0.0 { action_state.pressed_actions.insert(DirectionalNavigationAction::Left); }
|
if input.movement.x < 0.0 { action_state.pressed_actions.insert(DirectionalNavigationAction::Left); }
|
||||||
if input.movement.x > 0.0 { action_state.pressed_actions.insert(DirectionalNavigationAction::Right); }
|
if input.movement.x > 0.0 { action_state.pressed_actions.insert(DirectionalNavigationAction::Right); }
|
||||||
}
|
}
|
||||||
if input.accept { action_state.pressed_actions.insert(DirectionalNavigationAction::Select); }*/
|
if input.accept.is_pressed() { action_state.pressed_actions.insert(DirectionalNavigationAction::Select); }
|
||||||
}
|
}
|
||||||
|
|
||||||
const FOCUSED_BORDER: Srgba = bevy::color::palettes::tailwind::BLUE_50;
|
const FOCUSED_BORDER: Srgba = bevy::color::palettes::tailwind::BLUE_50;
|
||||||
@@ -117,7 +141,10 @@ pub fn navigate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn give_default_focus(query: Query<Entity, Added<NavigationDefaultFocus>>, mut input_focus: ResMut<InputFocus>) {
|
pub fn give_default_focus(
|
||||||
|
query: Query<Entity, Added<NavigationDefaultFocus>>,
|
||||||
|
mut input_focus: ResMut<InputFocus>,
|
||||||
|
) {
|
||||||
for entity in query {
|
for entity in query {
|
||||||
input_focus.set(entity, bevy::input_focus::FocusCause::Navigated);
|
input_focus.set(entity, bevy::input_focus::FocusCause::Navigated);
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-5
@@ -15,15 +15,35 @@ fn compute_ui_scale(window_size: Vec2) -> f32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn ui_update_scale_on_startup(window: Single<&Window>, mut ui_scale: ResMut<UiScale>) {
|
pub fn ui_update_scale_on_startup(window: Single<&Window>, mut ui_scale: ResMut<UiScale>) {
|
||||||
ui_scale.0 = compute_ui_scale(window.size());
|
ui_scale.0 = compute_ui_scale(window.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ui_scaling_system(mut resize_reader: MessageReader<WindowResized>, mut ui_scale: ResMut<UiScale>) {
|
pub fn ui_scaling_system(
|
||||||
|
mut resize_reader: MessageReader<WindowResized>,
|
||||||
|
mut ui_scale: ResMut<UiScale>,
|
||||||
|
) {
|
||||||
for event in resize_reader.read() {
|
for event in resize_reader.read() {
|
||||||
let dimensions = Vec2::new(event.width, event.height);
|
let dimensions = Vec2::new(event.width, event.height);
|
||||||
*ui_scale = UiScale(compute_ui_scale(dimensions));
|
*ui_scale = UiScale(compute_ui_scale(dimensions));
|
||||||
|
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn world_to_ui_position(pos: Vec2, camera: &Camera, camera_transform: &GlobalTransform, ui_scale: &UiScale) -> Vec2 {
|
||||||
|
let viewport_position = camera.world_to_viewport(camera_transform, Vec3::new(pos.x, pos.y, 0.0)).unwrap();
|
||||||
|
|
||||||
|
let viewport_rect = camera.logical_viewport_rect().unwrap();
|
||||||
|
|
||||||
|
let ui_position = (viewport_position - viewport_rect.min) / ui_scale.0;
|
||||||
|
|
||||||
|
ui_position
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn logical_to_ui_position(pos: Vec2, camera: &Camera, ui_scale: &UiScale) -> Vec2 {
|
||||||
|
let viewport_rect = camera.logical_viewport_rect().unwrap();
|
||||||
|
|
||||||
|
let ui_position = (pos - viewport_rect.min) / ui_scale.0;
|
||||||
|
|
||||||
|
ui_position
|
||||||
}
|
}
|
||||||
+25
-7
@@ -2,6 +2,9 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ron_asset_manager::prelude::*;
|
||||||
|
|
||||||
|
use crate::texture_reference::TextureReference;
|
||||||
|
|
||||||
pub const H1_SIZE: i32 = 72;
|
pub const H1_SIZE: i32 = 72;
|
||||||
pub const H2_SIZE: i32 = 54;
|
pub const H2_SIZE: i32 = 54;
|
||||||
@@ -15,7 +18,7 @@ pub struct BoxShadowStyle {
|
|||||||
offset_px_x: i32,
|
offset_px_x: i32,
|
||||||
offset_px_y: i32,
|
offset_px_y: i32,
|
||||||
spread_radius: f32,
|
spread_radius: f32,
|
||||||
blur_radius: f32
|
blur_radius: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Reflect, Default)]
|
#[derive(Serialize, Deserialize, Reflect, Default)]
|
||||||
@@ -25,7 +28,7 @@ pub struct ButtonStyle {
|
|||||||
border_color: Color,
|
border_color: Color,
|
||||||
text_color: Color,
|
text_color: Color,
|
||||||
box_shadow: BoxShadowStyle,
|
box_shadow: BoxShadowStyle,
|
||||||
border_size: i32
|
border_size: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Reflect)]
|
#[derive(Serialize, Deserialize, Reflect)]
|
||||||
@@ -44,7 +47,13 @@ impl ButtonStyle {
|
|||||||
commands.entity(entity).insert((
|
commands.entity(entity).insert((
|
||||||
BackgroundColor(self.background_color),
|
BackgroundColor(self.background_color),
|
||||||
BorderColor::all(self.background_color),
|
BorderColor::all(self.background_color),
|
||||||
BoxShadow::new(self.box_shadow.color, px(self.box_shadow.offset_px_x), px(self.box_shadow.offset_px_y), px(self.box_shadow.spread_radius), px(self.box_shadow.blur_radius))
|
BoxShadow::new(
|
||||||
|
self.box_shadow.color,
|
||||||
|
px(self.box_shadow.offset_px_x),
|
||||||
|
px(self.box_shadow.offset_px_y),
|
||||||
|
px(self.box_shadow.spread_radius),
|
||||||
|
px(self.box_shadow.blur_radius),
|
||||||
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,10 +62,19 @@ impl ButtonStyle {
|
|||||||
pub enum FGUiButtonType {
|
pub enum FGUiButtonType {
|
||||||
#[default]
|
#[default]
|
||||||
MainMenu,
|
MainMenu,
|
||||||
CharacterSelectCharacter
|
CharacterSelectCharacter,
|
||||||
|
CharacterSelectStartBanner,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Asset, Resource, Reflect, Deserialize, Serialize)]
|
#[derive(Asset, Resource, Reflect, Deserialize, RonAsset)]
|
||||||
pub struct FGUiStyle {
|
pub struct FGUiStyle {
|
||||||
pub button_styles: HashMap<FGUiButtonType, ButtonStyles>
|
pub button_styles: HashMap<FGUiButtonType, ButtonStyles>,
|
||||||
}
|
#[asset]
|
||||||
|
#[dependency]
|
||||||
|
#[reflect(ignore)]
|
||||||
|
pub cursor: TextureReference,
|
||||||
|
#[asset]
|
||||||
|
#[dependency]
|
||||||
|
#[reflect(ignore)]
|
||||||
|
pub css_token: TextureReference,
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
use bevy::{
|
||||||
|
asset::{LoadContext, UntypedAssetId, VisitAssetDependencies},
|
||||||
|
image::{ImageSampler, ImageSamplerDescriptor},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use ron_asset_manager::{Shandle, prelude::RonAsset};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Default)]
|
||||||
|
pub struct TextureReference {
|
||||||
|
pub path: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub sampling: TextureSampling,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub handle: Shandle<Image>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default, Deserialize)]
|
||||||
|
pub enum TextureSampling {
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
Linear,
|
||||||
|
Nearest,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextureReference {
|
||||||
|
pub fn handle(&self) -> &Handle<Image> {
|
||||||
|
&self.handle.handle
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(&mut self, load_context: &mut LoadContext<'_>) {
|
||||||
|
let sampling = self.sampling;
|
||||||
|
self.handle.handle = load_context
|
||||||
|
.load_builder()
|
||||||
|
.with_settings(move |settings: &mut bevy::image::ImageLoaderSettings| {
|
||||||
|
settings.sampler = match sampling {
|
||||||
|
TextureSampling::Default => ImageSampler::Default,
|
||||||
|
TextureSampling::Linear => {
|
||||||
|
ImageSampler::Descriptor(ImageSamplerDescriptor::linear())
|
||||||
|
}
|
||||||
|
TextureSampling::Nearest => {
|
||||||
|
ImageSampler::Descriptor(ImageSamplerDescriptor::nearest())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.load(self.path.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RonAsset for TextureReference {
|
||||||
|
fn load_assets(&mut self, load_context: &mut LoadContext<'_>) {
|
||||||
|
self.load(load_context);
|
||||||
|
self.handle.path = self.path.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitAssetDependencies for TextureReference {
|
||||||
|
fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) {
|
||||||
|
self.handle.handle.visit_dependencies(visit);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user