More CSS work

This commit is contained in:
2026-09-11 19:03:05 +02:00
parent cc850debf2
commit 1be4b470c6
17 changed files with 732 additions and 159 deletions
+21 -7
View File
@@ -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 {
fighter_manifest: Handle<FighterManifest>,
pub fighter_manifest: Handle<FighterManifest>,
pub fighter_id: FighterId
}
impl CharacterBox {
pub fn scene(manifest_handle: Handle<FighterManifest>, manifests: &Res<Assets<FighterManifest>>) -> impl Scene {
let manifest = manifests.get(&manifest_handle).expect("Fighter manifest should be valid");
pub fn scene(
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! {
Node {
width: px(128),
@@ -17,11 +27,15 @@ impl CharacterBox {
flex_direction: FlexDirection::Column,
justify_content: JustifyContent::FlexEnd
}
ImageNode {
image: fighter_icon
}
FGUiButton {
button_type: FGUiButtonType::CharacterSelectCharacter
}
CharacterBox {
fighter_manifest: manifest_handle
fighter_manifest: manifest_handle,
fighter_id: fighter_id
}
BackgroundColor(Color::srgb(1.0, 1.0, 1.0))
Children [
+3
View File
@@ -0,0 +1,3 @@
pub enum CharacterSelectErrors {
}
@@ -1,7 +1,6 @@
use bevy::prelude::*;
pub struct FighterPortrait {
pub player_slot: usize
pub player_slot: usize,
}
impl FighterPortrait {
@@ -15,4 +14,4 @@ impl FighterPortrait {
BackgroundColor(bevy::color::palettes::css::ORANGE)
]
}
}
}
+172 -34
View File
@@ -1,43 +1,53 @@
use bevy::prelude::*;
use bevy::{
picking::pointer::{PointerId, PointerLocation},
prelude::*,
};
use bevy_egui::egui::accesskit::TextAlign;
use crate::{
fighter::manifest::{FighterManifest, FighterManifestRegistry},
input::LocalInputAssignments,
menus::{MenuMarker, build_menu_with_props, cursor::FGMenuCursor, spawn_menu, wrap_menu},
AppState, fighter::manifest::{FighterManifest, FighterManifestRegistry}, game_settings::CommonAssets, input::{LocalInputAssignments, LocalInputSource}, menus::{
MenuMarker, build_menu_with_props,
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 errors;
pub mod fighter_portrait;
pub mod token;
#[derive(Component, Clone, Default)]
pub struct StartText;
#[derive(Component, FromTemplate)]
pub struct CharacterSelectScreen {
pub fighter_portait_container: Entity,
}
#[derive(Component, FromTemplate)]
pub struct StartButtonBanner;
pub fn setup_character_select(
existing_menu: Option<Single<Entity, With<MenuMarker>>>,
mut commands: Commands,
manifest_registry: Res<FighterManifestRegistry>,
assignments: Res<LocalInputAssignments>,
manifests: Res<Assets<FighterManifest>>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
common_assets: Res<CommonAssets>,
styles: Res<Assets<FGUiStyle>>,
) {
let character_boxes = manifest_registry
.iter()
.map(|(_, v)| character_box::CharacterBox::scene(v.clone(), &manifests))
.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! {
// Characters
CharacterSelectScreen {
@@ -47,7 +57,8 @@ pub fn setup_character_select(
flex_direction: FlexDirection::Column
}
Children [
(Node {
(
Node {
flex_direction: FlexDirection::Column,
width: percent(100),
height: percent(100),
@@ -55,7 +66,32 @@ pub fn setup_character_select(
align_items: AlignItems::Center
}
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 {
flex_direction: FlexDirection::Row,
width: percent(100),
height: px(400),
justify_content: JustifyContent::Center
}
Children [
{fighter_portraits}
]
)
]
};
commands.init_resource::<MenuMatchConfig>();
spawn_menu(existing_menu, commands, menu);
}
@@ -79,44 +114,58 @@ pub fn create_portrait(
player_slot: usize,
container: Entity,
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
style: &FGUiStyle,
input_source: &LocalInputSource,
) {
let entity = commands
.spawn_scene(fighter_portrait::FighterPortrait::create(player_slot))
.id();
commands.entity(container).add_child(entity);
commands.spawn(
FGMenuCursor::create_cursor(player_slot, &mut meshes, &mut materials)
);
let cursor = commands
.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)]
pub struct FighterSlotAssigned {
pub struct PlayerSlotAssigned {
pub slot: usize,
pub input_source: LocalInputSource,
}
pub fn handle_fighter_slot_addition(
addition: On<FighterSlotAssigned>,
addition: On<PlayerSlotAssigned>,
query: Single<&CharacterSelectScreen>,
commands: Commands,
meshes: ResMut<Assets<Mesh>>,
materials: ResMut<Assets<ColorMaterial>>,
common_assets: Res<CommonAssets>,
styles: Res<Assets<FGUiStyle>>,
) {
let slot = addition.slot;
create_portrait(
slot,
query.fighter_portait_container,
commands,
meshes,
materials,
styles
.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>,
mut assignments: ResMut<LocalInputAssignments>,
mut match_config: ResMut<MenuMatchConfig>,
mut commands: Commands,
) {
for (source, input) in &menu_inputs.inputs {
@@ -124,9 +173,98 @@ pub fn css_input(
let existed = assignments.iter().any(|(_, assigned)| assigned == source);
if !existed && let Some(free_idx) = assignments.get_free_player_index() {
assignments.push((free_idx, *source));
match_config.players.push(MenuMatchPlayerSlot {
slot: free_idx,
fighter: None,
});
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(())
}
+33
View File
@@ -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()
}
)
}
}