loby ui impl
This commit is contained in:
+268
-78
@@ -4,10 +4,11 @@ use bevy::{
|
||||
prelude::*,
|
||||
text::{EditableText, EditableTextFilter, TextCursorStyle},
|
||||
ui::Checked,
|
||||
ui_widgets::{RadioButton, RadioGroup, ValueChange},
|
||||
ui_widgets::{Activate, Button, RadioButton, RadioGroup, ValueChange},
|
||||
};
|
||||
|
||||
use crate::AppState;
|
||||
use crate::main_menu::InteractionColors;
|
||||
|
||||
const PANEL_BACKGROUND: Color = Color::srgb(0.4, 0.4, 0.4);
|
||||
const TEXT_COLOR: Color = Color::srgb(0.9, 0.9, 0.9);
|
||||
@@ -23,16 +24,23 @@ const OPTION_SELECTED_PRESSED_BACKGROUND: Color = Color::srgb(0.07, 0.36, 0.44);
|
||||
|
||||
pub struct LobbyPlugin;
|
||||
|
||||
#[derive(Resource, Debug, Clone, Copy, PartialEq, Eq, Reflect)]
|
||||
#[derive(Resource, Debug, Clone, PartialEq, Eq, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
pub struct LobbyUiState {
|
||||
pub privacy: LobbyPrivacy,
|
||||
pub port: u16,
|
||||
pub password: String,
|
||||
// TODO: replace with actual connection logic
|
||||
pub session_active: bool,
|
||||
}
|
||||
|
||||
impl Default for LobbyUiState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
privacy: LobbyPrivacy::Public,
|
||||
port: 5599,
|
||||
password: String::new(),
|
||||
session_active: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,14 +48,26 @@ impl Default for LobbyUiState {
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Reflect)]
|
||||
pub enum LobbyPrivacy {
|
||||
#[default]
|
||||
Public,
|
||||
FriendsOnly,
|
||||
InviteOnly,
|
||||
FriendsOnly,
|
||||
Public,
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
struct LobbyPrivacyOption(LobbyPrivacy);
|
||||
|
||||
#[derive(Component, Clone, Default)]
|
||||
struct PortField;
|
||||
|
||||
#[derive(Component, Clone, Default)]
|
||||
struct PasswordField;
|
||||
|
||||
#[derive(Component, Clone, Default)]
|
||||
struct ResetPortButton;
|
||||
|
||||
#[derive(Component, Clone, Default)]
|
||||
struct SessionStatusLabel;
|
||||
|
||||
impl Plugin for LobbyPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<LobbyUiState>();
|
||||
@@ -58,7 +78,13 @@ impl Plugin for LobbyPlugin {
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
(update_privacy_controls, style_privacy_options).chain(),
|
||||
(
|
||||
update_privacy_controls,
|
||||
session_visuals,
|
||||
sync_lobby_fields,
|
||||
style_privacy_options,
|
||||
)
|
||||
.chain(),
|
||||
);
|
||||
app.add_observer(on_privacy_changed);
|
||||
}
|
||||
@@ -88,13 +114,12 @@ fn setup_lobby(mut commands: Commands) {
|
||||
|
||||
fn build_lobby_ui(mut commands: Commands, root_ui: Single<Entity, With<LobbyUiRoot>>) {
|
||||
let root_ui = *root_ui;
|
||||
|
||||
commands.spawn_scene(bsn! {
|
||||
ChildOf(root_ui)
|
||||
Node {
|
||||
width: percent(50),
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_self: AlignSelf::FlexEnd,
|
||||
flex_grow: 1.,
|
||||
align_self: AlignSelf::Stretch,
|
||||
border_radius: BorderRadius::all(px(24)),
|
||||
padding: Val::all(px(24)),
|
||||
}
|
||||
@@ -106,122 +131,283 @@ fn build_lobby_ui(mut commands: Commands, root_ui: Single<Entity, With<LobbyUiRo
|
||||
align_self: AlignSelf::Center,
|
||||
}
|
||||
TextFont {
|
||||
font_size: px(100.0),
|
||||
font_size: px(76.),
|
||||
}
|
||||
TextColor(TEXT_COLOR)
|
||||
TextShadow
|
||||
),
|
||||
(
|
||||
Text("Session Visibility")
|
||||
Node {
|
||||
margin: UiRect::top(px(24)),
|
||||
}
|
||||
TextFont {
|
||||
font_size: px(38.),
|
||||
}
|
||||
TextColor(TEXT_COLOR)
|
||||
),
|
||||
(
|
||||
RadioGroup
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Row,
|
||||
width: px(900),
|
||||
height: px(110),
|
||||
width: percent(100),
|
||||
height: px(90),
|
||||
}
|
||||
TabIndex(0)
|
||||
Children [
|
||||
privacy_option("Public", LobbyPrivacy::Public, BorderRadius::px(16., 0., 0., 16.)),
|
||||
privacy_option(
|
||||
"Public",
|
||||
LobbyPrivacy::Public,
|
||||
BorderRadius::px(16., 0., 0., 16.),
|
||||
),
|
||||
privacy_option("Friends Only", LobbyPrivacy::FriendsOnly, BorderRadius::ZERO),
|
||||
privacy_option("Invite Only", LobbyPrivacy::InviteOnly, BorderRadius::px(0., 16., 16., 0.)),
|
||||
privacy_option(
|
||||
"Invite Only",
|
||||
LobbyPrivacy::InviteOnly,
|
||||
BorderRadius::px(0., 16., 16., 0.),
|
||||
),
|
||||
]
|
||||
),
|
||||
(
|
||||
Text("Save Name")
|
||||
Node {
|
||||
margin: UiRect::axes(px(12), px(0)),
|
||||
}
|
||||
TextFont {
|
||||
// font: FontSourceTemplate::Handle("fonts/FiraSans-Bold.ttf"),
|
||||
font_size: px(66.0),
|
||||
}
|
||||
TextColor(TEXT_COLOR)
|
||||
TextShadow
|
||||
field_label("Port")
|
||||
),
|
||||
(
|
||||
Node {
|
||||
border: Val::all(px(6)),
|
||||
padding: Val::all(px(12)),
|
||||
border_radius: BorderRadius::all(px(12)),
|
||||
flex_direction: FlexDirection::Row,
|
||||
column_gap: px(12),
|
||||
align_items: AlignItems::Center,
|
||||
}
|
||||
BorderColor::from(SLATE_900)
|
||||
EditableText {
|
||||
cursor_width: 0.05,
|
||||
max_characters: Option::Some(14),
|
||||
Children [
|
||||
port_field(),
|
||||
(
|
||||
Button
|
||||
ResetPortButton
|
||||
Node {
|
||||
width: px(600),
|
||||
height: px(130),
|
||||
border: px(6),
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
margin: UiRect::all(px(4)),
|
||||
}
|
||||
BorderColor::from(Color::BLACK)
|
||||
InteractionColors::DEFAULT
|
||||
Interaction
|
||||
Children [(
|
||||
Text("Reset")
|
||||
TextFont {
|
||||
font_size: px(66.),
|
||||
}
|
||||
TextColor(TEXT_COLOR)
|
||||
TextShadow
|
||||
)]
|
||||
on(|_event: On<Activate>, mut fields: Query<&mut EditableText, With<PortField>>| {
|
||||
if let Ok(mut field) = fields.single_mut() {
|
||||
field.editor_mut().set_text("5599");
|
||||
}
|
||||
})
|
||||
),
|
||||
]
|
||||
),
|
||||
(field_label("Password")),
|
||||
(password_field()),
|
||||
(
|
||||
Node {
|
||||
flex_grow: 1.,
|
||||
}
|
||||
EditableTextFilter::new(|c| c == ' ' || c.is_ascii_alphanumeric())
|
||||
TextLayout::no_wrap()
|
||||
TextFont {
|
||||
font_size: FontSize::Px(66.),
|
||||
),
|
||||
(
|
||||
Button
|
||||
Node {
|
||||
width: px(600),
|
||||
height: px(130),
|
||||
border: px(6),
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
margin: UiRect::all(px(4)),
|
||||
}
|
||||
TextCursorStyle {
|
||||
color: Color::BLACK
|
||||
}
|
||||
TabIndex(0)
|
||||
BackgroundColor(TEXT_INPUT_BACKGROUND)
|
||||
BorderColor::from(Color::BLACK)
|
||||
InteractionColors::GOOD
|
||||
Interaction
|
||||
on(|_event: On<Activate>, mut lobby: ResMut<LobbyUiState>| {
|
||||
info!("Session clicked!");
|
||||
lobby.session_active = !lobby.session_active;
|
||||
})
|
||||
Children [(
|
||||
Text("Start Session")
|
||||
SessionStatusLabel
|
||||
TextFont {
|
||||
font_size: px(66.),
|
||||
}
|
||||
TextColor(TEXT_COLOR)
|
||||
TextShadow
|
||||
)]
|
||||
),
|
||||
]
|
||||
});
|
||||
|
||||
commands.spawn_scene(bsn! {
|
||||
ChildOf(root_ui)
|
||||
Node {
|
||||
width: percent(50),
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_self: AlignSelf::FlexEnd,
|
||||
flex_grow: 1.,
|
||||
align_self: AlignSelf::Stretch,
|
||||
border_radius: BorderRadius::all(px(24)),
|
||||
padding: Val::all(px(24)),
|
||||
}
|
||||
BackgroundColor(PANEL_BACKGROUND)
|
||||
Children [
|
||||
(
|
||||
Text("Lobby")
|
||||
Text("Connected Players")
|
||||
Node {
|
||||
align_self: AlignSelf::Center,
|
||||
}
|
||||
TextFont {
|
||||
font_size: px(100.0),
|
||||
font_size: px(76.),
|
||||
}
|
||||
TextColor(TEXT_COLOR)
|
||||
TextShadow
|
||||
),
|
||||
(
|
||||
Text("Save Name")
|
||||
Node {
|
||||
margin: UiRect::axes(px(12), px(0)),
|
||||
}
|
||||
TextFont {
|
||||
// font: FontSourceTemplate::Handle("fonts/FiraSans-Bold.ttf"),
|
||||
font_size: px(66.0),
|
||||
}
|
||||
TextColor(TEXT_COLOR)
|
||||
TextShadow
|
||||
),
|
||||
(
|
||||
Node {
|
||||
border: Val::all(px(6)),
|
||||
padding: Val::all(px(12)),
|
||||
border_radius: BorderRadius::all(px(12)),
|
||||
}
|
||||
BorderColor::from(SLATE_900)
|
||||
EditableText {
|
||||
cursor_width: 0.05,
|
||||
max_characters: Option::Some(14),
|
||||
}
|
||||
EditableTextFilter::new(|c| c == ' ' || c.is_ascii_alphanumeric())
|
||||
TextLayout::no_wrap()
|
||||
TextFont {
|
||||
font_size: FontSize::Px(66.),
|
||||
}
|
||||
TextCursorStyle {
|
||||
color: Color::BLACK
|
||||
}
|
||||
TabIndex(0)
|
||||
BackgroundColor(TEXT_INPUT_BACKGROUND)
|
||||
),
|
||||
player_row("Joe (you)"),
|
||||
player_row("Alex"),
|
||||
player_row("Sam"),
|
||||
player_row("Taylor"),
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
fn field_label(label: &'static str) -> impl Scene {
|
||||
bsn! {
|
||||
Text(label)
|
||||
Node {
|
||||
margin: UiRect::top(px(18)),
|
||||
}
|
||||
TextFont {
|
||||
font_size: px(38.),
|
||||
}
|
||||
TextColor(TEXT_COLOR)
|
||||
}
|
||||
}
|
||||
|
||||
fn port_field() -> impl Scene {
|
||||
bsn! {
|
||||
PortField
|
||||
Node {
|
||||
width: percent(100),
|
||||
border: Val::all(px(6)),
|
||||
padding: Val::all(px(12)),
|
||||
border_radius: BorderRadius::all(px(12)),
|
||||
}
|
||||
BorderColor::from(SLATE_900)
|
||||
EditableText::new("5599")
|
||||
EditableText {
|
||||
cursor_width: 0.05,
|
||||
max_characters: Option::Some(5),
|
||||
}
|
||||
EditableTextFilter::new(|c| c.is_ascii_digit())
|
||||
TextLayout::no_wrap()
|
||||
TextFont {
|
||||
font_size: FontSize::Px(66.),
|
||||
}
|
||||
TextCursorStyle {
|
||||
color: Color::BLACK,
|
||||
}
|
||||
TabIndex(0)
|
||||
BackgroundColor(TEXT_INPUT_BACKGROUND)
|
||||
}
|
||||
}
|
||||
fn password_field() -> impl Scene {
|
||||
bsn! {
|
||||
PasswordField
|
||||
Node {
|
||||
width: percent(100),
|
||||
border: Val::all(px(6)),
|
||||
padding: Val::all(px(12)),
|
||||
border_radius: BorderRadius::all(px(12)),
|
||||
}
|
||||
BorderColor::from(SLATE_900)
|
||||
EditableText {
|
||||
cursor_width: 0.05,
|
||||
max_characters: Option::Some(32),
|
||||
}
|
||||
EditableTextFilter::new(|c| c == ' ' || c.is_ascii_alphanumeric() || c == '_' || c == '-')
|
||||
TextLayout::no_wrap()
|
||||
TextFont {
|
||||
font_size: FontSize::Px(66.),
|
||||
}
|
||||
TextCursorStyle {
|
||||
color: Color::BLACK,
|
||||
}
|
||||
TabIndex(1)
|
||||
BackgroundColor(TEXT_INPUT_BACKGROUND)
|
||||
}
|
||||
}
|
||||
|
||||
fn player_row(name: &'static str) -> impl Scene {
|
||||
bsn! {
|
||||
Node {
|
||||
width: percent(100),
|
||||
height: px(130),
|
||||
margin: UiRect::top(px(14)),
|
||||
padding: Val::all(px(16)),
|
||||
align_items: AlignItems::Center,
|
||||
column_gap: px(24),
|
||||
border_radius: BorderRadius::all(px(12)),
|
||||
}
|
||||
BackgroundColor(OPTION_BACKGROUND)
|
||||
Children [
|
||||
(
|
||||
Node {
|
||||
width: px(96),
|
||||
height: px(96),
|
||||
border_radius: BorderRadius::MAX,
|
||||
}
|
||||
BackgroundColor(OPTION_SELECTED_BACKGROUND)
|
||||
),
|
||||
(
|
||||
Text(name)
|
||||
TextFont {
|
||||
font_size: px(50.),
|
||||
}
|
||||
TextColor(TEXT_COLOR)
|
||||
TextShadow
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_lobby_fields(
|
||||
port_field: Single<&EditableText, With<PortField>>,
|
||||
password_field: Single<&EditableText, With<PasswordField>>,
|
||||
mut lobby: ResMut<LobbyUiState>,
|
||||
) {
|
||||
if let Ok(port) = port_field.value().to_string().parse::<u16>() {
|
||||
lobby.port = port;
|
||||
}
|
||||
lobby.password = password_field.value().to_string();
|
||||
}
|
||||
|
||||
fn session_visuals(
|
||||
lobby_ui: Res<LobbyUiState>,
|
||||
q_session_control_text: Option<Single<&mut Text, With<SessionStatusLabel>>>,
|
||||
) {
|
||||
if !lobby_ui.is_changed() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(mut text) = q_session_control_text else {
|
||||
warn!("Session Control Text not found!");
|
||||
return;
|
||||
};
|
||||
|
||||
let new_text = match lobby_ui.session_active {
|
||||
true => "Stop Session",
|
||||
false => "Start Session",
|
||||
};
|
||||
**text = new_text.into();
|
||||
}
|
||||
|
||||
fn privacy_option(
|
||||
label: &'static str,
|
||||
privacy: LobbyPrivacy,
|
||||
@@ -236,7 +422,7 @@ fn privacy_option(
|
||||
flex_grow: 1.,
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
border: UiRect::all(px(3)),
|
||||
border: UiRect::all(px(4)),
|
||||
border_radius,
|
||||
}
|
||||
BorderColor::all(OPTION_BORDER_COLOR)
|
||||
@@ -257,6 +443,10 @@ fn update_privacy_controls(
|
||||
mut commands: Commands,
|
||||
options: Query<(Entity, &LobbyPrivacyOption, &Interaction, Has<Checked>), With<RadioButton>>,
|
||||
) {
|
||||
if !lobby_ui.is_changed() {
|
||||
return;
|
||||
}
|
||||
|
||||
for (entity, option, interaction, checked) in &options {
|
||||
let should_be_checked = option.0 == lobby_ui.privacy;
|
||||
if should_be_checked && !checked {
|
||||
|
||||
Reference in New Issue
Block a user