DEVELOPMENT ENVIRONMENT

~liljamo/deck-builder

266a4f354ad61f6acba8a12409aff529887b7c55 — Jonni Liljamo 1 year, 5 months ago e09fcc3
chore(client): fmt, clippy, cleanup
M client/src/api/game/create_action.rs => client/src/api/game/create_action.rs +13 -12
@@ 8,7 8,7 @@

use serde::Serialize;

use crate::{NetworkingOptions, post_request_auth};
use crate::{post_request_auth, NetworkingOptions};

use super::Action;



@@ 23,17 23,18 @@ struct CreateActionPost {
    seed: String,
}

pub fn create_action(
    no: &NetworkingOptions,
    action: &Action,
) -> CreateActionResponse {
    let res = post_request_auth!(no, "/game/action", &CreateActionPost {
        game_id: action.game_id.to_string(),
        invoker: action.invoker.to_string(),
        target: action.target.to_string(),
        command: serde_json::to_string(&action.command).unwrap(),
        seed: action.seed.to_string(),
    });
pub fn create_action(no: &NetworkingOptions, action: &Action) -> CreateActionResponse {
    let res = post_request_auth!(
        no,
        "/game/action",
        &CreateActionPost {
            game_id: action.game_id.to_string(),
            invoker: action.invoker.to_string(),
            target: action.target.to_string(),
            command: serde_json::to_string(&action.command).unwrap(),
            seed: action.seed.to_string(),
        }
    );

    Ok(res.json().unwrap())
}

M client/src/api/game/details.rs => client/src/api/game/details.rs +1 -1
@@ 6,7 6,7 @@
 * See LICENSE for licensing information.
 */

use crate::{NetworkingOptions, get_request_auth};
use crate::{get_request_auth, NetworkingOptions};

use super::Game;


M client/src/api/game/forming.rs => client/src/api/game/forming.rs +1 -1
@@ 6,7 6,7 @@
 * See LICENSE for licensing information.
 */

use crate::{NetworkingOptions, get_request_auth};
use crate::{get_request_auth, NetworkingOptions};

use super::Game;


M client/src/api/game/join.rs => client/src/api/game/join.rs +1 -1
@@ 6,7 6,7 @@
 * See LICENSE for licensing information.
 */

use crate::{NetworkingOptions, post_request_auth};
use crate::{post_request_auth, NetworkingOptions};

// NOTE: this response isn't actually used
pub type JoinResponse = Result<String, ()>;

M client/src/api/game/mod.rs => client/src/api/game/mod.rs +20 -14
@@ 33,17 33,29 @@ pub use details::*;

#[derive(Deserialize, Serialize, Clone)]
pub enum Command {
    InitSupplyPile { card: Card, amount: usize },
    InitSupplyPile {
        card: Card,
        amount: usize,
    },
    /// take a card from pile N
    TakeFromPile { index: usize, for_cost: i16 },
    TakeFromPile {
        index: usize,
        for_cost: i16,
    },
    /// draw N amount of cards from deck
    Draw { amount: usize },
    Draw {
        amount: usize,
    },
    /// discard card from hand in slot N
    Discard { index: usize },
    Discard {
        index: usize,
    },
    /// end the targets turn
    EndTurn { },
    EndTurn {},
    /// change player state to another
    ChangePlayerState { state: PlayerState },
    ChangePlayerState {
        state: PlayerState,
    },
}

#[derive(Deserialize, Serialize, Clone)]


@@ 60,13 72,7 @@ pub struct Action {
}

impl Action {
    pub fn new(
        game_id: &str,
        invoker: &str,
        target: &str,
        command: &Command,
        seed: u64
    ) -> Self {
    pub fn new(game_id: &str, invoker: &str, target: &str, command: &Command, seed: u64) -> Self {
        Self {
            id: "".to_string(),
            created_at: chrono::Utc::now(),


@@ 84,7 90,7 @@ impl Action {
#[repr(u8)]
pub enum GameState {
    Forming = 0,
    InProgress= 1,
    InProgress = 1,
    Finished = 2,
    Cancelled = 3,
}

M client/src/api/game/my_games.rs => client/src/api/game/my_games.rs +1 -1
@@ 6,7 6,7 @@
 * See LICENSE for licensing information.
 */

use crate::{NetworkingOptions, get_request_auth};
use crate::{get_request_auth, NetworkingOptions};

use super::Game;


M client/src/api/user/login.rs => client/src/api/user/login.rs +9 -5
@@ 8,7 8,7 @@

use serde::Serialize;

use crate::{NetworkingOptions, post_request};
use crate::{post_request, NetworkingOptions};

use super::UserToken;



@@ 21,10 21,14 @@ struct LoginPost {
}

pub fn login(no: &NetworkingOptions, email: &str, password: &str) -> LoginResponse {
    let res = post_request!(no, "/user/token", &LoginPost {
        email: email.to_string(),
        password: password.to_string(),
    });
    let res = post_request!(
        no,
        "/user/token",
        &LoginPost {
            email: email.to_string(),
            password: password.to_string(),
        }
    );

    Ok(res.json().unwrap())
}

M client/src/api/user/register.rs => client/src/api/user/register.rs +10 -6
@@ 8,7 8,7 @@

use serde::Serialize;

use crate::{NetworkingOptions, post_request};
use crate::{post_request, NetworkingOptions};

use super::User;



@@ 27,11 27,15 @@ pub fn register(
    email: &str,
    password: &str,
) -> RegisterResponse {
    let res = post_request!(no, "/user", &RegisterPost {
        username: username.to_string(),
        email: email.to_string(),
        password: password.to_string(),
    });
    let res = post_request!(
        no,
        "/user",
        &RegisterPost {
            username: username.to_string(),
            email: email.to_string(),
            password: password.to_string(),
        }
    );

    Ok(res.json().unwrap())
}

M client/src/game_status/parser.rs => client/src/game_status/parser.rs +41 -28
@@ 10,15 10,18 @@ use std::collections::HashMap;

use fastrand::Rng;

use crate::{api::game::{Action, Command, Game}, game_status::SupplyPile};
use crate::{
    api::game::{Command, Game},
    game_status::SupplyPile,
};

use super::{GameStatus, PlayerState, PlayerStatus, Card};
use super::{GameStatus, PlayerState, PlayerStatus};

/// funny unsafe wrapper
fn get_invoker_target<'a, K, V>(
    players: &'a mut HashMap<K, V>,
    invoker: &K,
    target: &K
    target: &K,
) -> (&'a mut V, &'a mut V)
where
    K: Eq + std::hash::Hash,


@@ 42,33 45,40 @@ pub fn parse(game: &Game) -> Result<GameStatus, ()> {
    let mut game_status = GameStatus {
        actions: game.actions.as_ref().unwrap().to_vec(),
        supply_piles: vec![],
        players: HashMap::new()
        players: HashMap::new(),
    };

    game_status.players.insert(game.host_id.clone(), PlayerStatus {
        state: PlayerState::Idle,
        currency: 0,
        vp: 2,
        hand: vec![],
        deck: vec![],
        discard: vec![]
    });

    game_status.players.insert(game.guest_id.clone(), PlayerStatus {
        state: PlayerState::Idle,
        currency: 0,
        vp: 2,
        hand: vec![],
        deck: vec![],
        discard: vec![]
    });
    game_status.players.insert(
        game.host_id.clone(),
        PlayerStatus {
            state: PlayerState::Idle,
            currency: 0,
            vp: 2,
            hand: vec![],
            deck: vec![],
            discard: vec![],
        },
    );

    game_status.players.insert(
        game.guest_id.clone(),
        PlayerStatus {
            state: PlayerState::Idle,
            currency: 0,
            vp: 2,
            hand: vec![],
            deck: vec![],
            discard: vec![],
        },
    );

    // TODO: a system for reparsing if needed, e.g. after something
    // modifies the actions Vector.
    for action in &game_status.actions {
        // invoker: the one who invoked the action
        // target: the one who the action affects, may also be the invoker, e.g. draw
        let (invoker, target) = get_invoker_target(&mut game_status.players, &action.invoker, &action.target);
        let (invoker, target) =
            get_invoker_target(&mut game_status.players, &action.invoker, &action.target);

        match &action.command {
            Command::InitSupplyPile { card, amount } => {


@@ 82,16 92,18 @@ pub fn parse(game: &Game) -> Result<GameStatus, ()> {
            Command::TakeFromPile { index, for_cost } => {
                // index should be within range
                assert!(*index <= game_status.supply_piles.len());
                let pile = &mut game_status.supply_piles.get_mut(*index)
                let pile = &mut game_status
                    .supply_piles
                    .get_mut(*index)
                    .unwrap_or_else(|| unreachable!());

                // pile should not be empty
                assert!(pile.amount > 0);
                pile.amount = pile.amount - 1;
                pile.amount -= 1;

                // player should have enough
                assert!(*for_cost <= target.currency);
                target.currency = target.currency - for_cost;
                target.currency -= for_cost;

                target.discard.push(pile.card.clone());
            }


@@ 101,8 113,9 @@ pub fn parse(game: &Game) -> Result<GameStatus, ()> {
                        shuffle_discard_to_deck(target, action.seed.parse::<u64>().unwrap());
                    }

                    target.hand.push(target.deck.pop()
                                     .unwrap_or_else(|| unreachable!()));
                    target
                        .hand
                        .push(target.deck.pop().unwrap_or_else(|| unreachable!()));
                }
            }
            Command::Discard { index } => {


@@ 110,7 123,7 @@ pub fn parse(game: &Game) -> Result<GameStatus, ()> {
                assert!(*index <= target.hand.len());
                target.discard.push(target.hand.remove(*index));
            }
            Command::EndTurn {  } => {
            Command::EndTurn {} => {
                // NOTE: target will be the next player

                // set player to idle

M client/src/macros/async_task.rs => client/src/macros/async_task.rs +5 -15
@@ 9,16 9,11 @@
#[macro_export]
macro_rules! async_task_start_call {
    ($call_event_type:ty, $call_type:expr, |$ev:ident| $func:expr) => {
        pub fn start_call(
            mut commands: Commands,
            mut start_ev_r: EventReader<$call_event_type>,
        ) {
        pub fn start_call(mut commands: Commands, mut start_ev_r: EventReader<$call_event_type>) {
            for ev in start_ev_r.iter() {
                let thread_pool = AsyncComputeTaskPool::get();
                let $ev = ev.clone();
                let task = thread_pool.spawn(async move {
                    $func
                });
                let task = thread_pool.spawn(async move { $func });
                commands.spawn($call_type(task));
            }
        }


@@ 28,15 23,13 @@ macro_rules! async_task_start_call {
        pub fn start_call(
            mut commands: Commands,
            mut start_ev_r: EventReader<$call_event_type>,
            no: Res<NetworkingOptions>
            no: Res<NetworkingOptions>,
        ) {
            for ev in start_ev_r.iter() {
                let thread_pool = AsyncComputeTaskPool::get();
                let $ev = ev.clone();
                let $no = no.clone();
                let task = thread_pool.spawn(async move {
                    $func
                });
                let task = thread_pool.spawn(async move { $func });
                commands.spawn($call_type(task));
            }
        }


@@ 46,10 39,7 @@ macro_rules! async_task_start_call {
#[macro_export]
macro_rules! async_task_handle_call {
    ($call_type:ty, |$response:ident| $handler_func:expr) => {
        pub fn handle_call(
            mut commands: Commands,
            mut tasks: Query<(Entity, &mut $call_type)>,
        ) {
        pub fn handle_call(mut commands: Commands, mut tasks: Query<(Entity, &mut $call_type)>) {
            match tasks.get_single_mut() {
                Ok((entity, mut task)) => {
                    if let Some($response) = future::block_on(future::poll_once(&mut task.0)) {

M client/src/main.rs => client/src/main.rs +13 -9
@@ 6,11 6,14 @@
 * See LICENSE for licensing information.
 */

#![allow(clippy::too_many_arguments)]
#![allow(clippy::derivable_impls)]

use api::user::User;
use bevy::prelude::*;
use bevy_rapier3d::prelude::*;
use bevy_editor_pls::EditorPlugin;
use bevy_egui::EguiPlugin;
use bevy_rapier3d::prelude::*;

mod api;
mod macros;


@@ 85,15 88,16 @@ fn main() {
struct PlayerCamera;

fn setup(mut commands: Commands) {
    commands.spawn(Camera3dBundle {
        transform: Transform {
            translation: Vec3::new(0., 8., 8.),
            rotation: Quat::from_rotation_x(-0.5),
    commands
        .spawn(Camera3dBundle {
            transform: Transform {
                translation: Vec3::new(0., 8., 8.),
                rotation: Quat::from_rotation_x(-0.5),
                ..Default::default()
            },
            ..Default::default()
        },
        ..Default::default()
    })
    .insert(PlayerCamera);
        })
        .insert(PlayerCamera);
}

fn editor_controls() -> bevy_editor_pls::controls::EditorControls {

M client/src/plugins/async_tasks/mod.rs => client/src/plugins/async_tasks/mod.rs +28 -28
@@ 39,8 39,7 @@ pub struct AsyncTasksPlugin;

impl Plugin for AsyncTasksPlugin {
    fn build(&self, app: &mut App) {
        app
            .add_event::<LoginCallEvent>()
        app.add_event::<LoginCallEvent>()
            .add_event::<RegisterCallEvent>()
            .add_event::<GameCreateCallEvent>()
            .add_event::<GameJoinCallEvent>()


@@ 50,36 49,37 @@ impl Plugin for AsyncTasksPlugin {
            .add_event::<GameActionCreateCallEvent>()
            .add_event::<ParseGameStatusEvent>()
            .add_systems(
            (
                req_login::start_call,
                req_login::handle_call,
                req_register::start_call,
                req_register::handle_call,
            )
            .chain()
                (
                    req_login::start_call,
                    req_login::handle_call,
                    req_register::start_call,
                    req_register::handle_call,
                )
                    .chain(),
            )
            .add_systems(
            (
                req_game_create::start_call,
                req_game_create::handle_call,
                req_game_join::start_call,
                req_game_join::handle_call,
                req_game_forming::start_call,
                req_game_forming::handle_call,
                req_game_mygames::start_call,
                req_game_mygames::handle_call,
                req_game_details::start_call,
                req_game_details::handle_call,
                req_game_action_create::start_call,
                req_game_action_create::handle_call,
            )
            .chain()
                (
                    req_game_create::start_call,
                    req_game_create::handle_call,
                    req_game_join::start_call,
                    req_game_join::handle_call,
                    req_game_forming::start_call,
                    req_game_forming::handle_call,
                    req_game_mygames::start_call,
                    req_game_mygames::handle_call,
                    req_game_details::start_call,
                    req_game_details::handle_call,
                    req_game_action_create::start_call,
                    req_game_action_create::handle_call,
                )
                    .chain(),
            )
            .add_systems(
            (
                parse_game_status::start_call,
                parse_game_status::handle_call,
            ).chain()
                (
                    parse_game_status::start_call,
                    parse_game_status::handle_call,
                )
                    .chain(),
            );
    }
}

M client/src/plugins/async_tasks/parse_game_status.rs => client/src/plugins/async_tasks/parse_game_status.rs +16 -26
@@ 6,11 6,12 @@
 * See LICENSE for licensing information.
 */

use crate::{
    plugins::game::GameData, game_status::GameStatus, api::game::Game,
};
use crate::{api::game::Game, game_status::GameStatus, plugins::game::GameData};

use bevy::{prelude::*, tasks::{Task, AsyncComputeTaskPool}};
use bevy::{
    prelude::*,
    tasks::{AsyncComputeTaskPool, Task},
};
use futures_lite::future;

#[derive(Component)]


@@ 21,18 22,11 @@ pub struct ParseGameStatusEvent {
    pub game: Game,
}

pub fn start_call(
    mut commands: Commands,
    mut start_ev_r: EventReader<ParseGameStatusEvent>,
    ) {
pub fn start_call(mut commands: Commands, mut start_ev_r: EventReader<ParseGameStatusEvent>) {
    for ev in start_ev_r.iter() {
        let thread_pool = AsyncComputeTaskPool::get();
        let ev = ev.clone();
        let task = thread_pool.spawn(async move {
            let res = GameStatus::new(&ev.game);

            res
        });
        let task = thread_pool.spawn(async move { GameStatus::new(&ev.game) });
        commands.spawn(ParseGameStatus(task));
    }
}


@@ 41,19 35,15 @@ pub fn handle_call(
    mut commands: Commands,
    mut tasks: Query<(Entity, &mut ParseGameStatus)>,
    mut game_data: ResMut<GameData>,
   ) {
    match tasks.get_single_mut() {
        Ok((entity, mut task)) => {
            if let Some(response) = future::block_on(future::poll_once(&mut task.0)) {
                game_data.game_status = Some(response);
                game_data.parsing_data = false;

                // remove the task
                commands.entity(entity).remove::<ParseGameStatus>();
                commands.entity(entity).despawn_recursive();
            }
) {
    if let Ok((entity, mut task)) = tasks.get_single_mut() {
        if let Some(response) = future::block_on(future::poll_once(&mut task.0)) {
            game_data.game_status = Some(response);
            game_data.parsing_data = false;

            // remove the task
            commands.entity(entity).remove::<ParseGameStatus>();
            commands.entity(entity).despawn_recursive();
        }
        // NOTE: don't do anything if the wanted thingy doesn't exist
        _ => {}
    }
}

M client/src/plugins/async_tasks/req_game_action_create.rs => client/src/plugins/async_tasks/req_game_action_create.rs +21 -13
@@ 7,12 7,19 @@
 */

use crate::{
    async_task_start_call, async_task_handle_call,
    api::{self, game::{Action, CreateActionResponse}},
    NetworkingOptions, plugins::GameData,
    api::{
        self,
        game::{Action, CreateActionResponse},
    },
    async_task_handle_call, async_task_start_call,
    plugins::GameData,
    NetworkingOptions,
};

use bevy::{prelude::*, tasks::{Task, AsyncComputeTaskPool}};
use bevy::{
    prelude::*,
    tasks::{AsyncComputeTaskPool, Task},
};
use futures_lite::future;

use super::ParseGameStatusEvent;


@@ 26,16 33,17 @@ pub struct GameActionCreateCallEvent {
}

async_task_start_call!(GameActionCreateCallEvent, GameActionCreateCall, |ev, no| {
    let res = api::game::create_action(&no, &ev.action);

    res
    api::game::create_action(&no, &ev.action)
});

async_task_handle_call!(GameActionCreateCall, |_u0, _u1, response, _game_data, _u2| {
    match response {
        Err(_err) => panic!("login failed, handle me"),
        Ok(resp) => {
            info!("created action {}", resp.id);
async_task_handle_call!(
    GameActionCreateCall,
    |_u0, _u1, response, _game_data, _u2| {
        match response {
            Err(_err) => panic!("login failed, handle me"),
            Ok(resp) => {
                info!("created action {}", resp.id);
            }
        }
    }
});
);

M client/src/plugins/async_tasks/req_game_create.rs => client/src/plugins/async_tasks/req_game_create.rs +8 -6
@@ 7,12 7,16 @@
 */

use crate::{
    async_task_start_call, async_task_handle_call,
    api::{self, game::CreateResponse}, NetworkingOptions,
    api::{self, game::CreateResponse},
    async_task_handle_call, async_task_start_call,
    plugins::menu::MenuData,
    NetworkingOptions,
};

use bevy::{prelude::*, tasks::{Task, AsyncComputeTaskPool}};
use bevy::{
    prelude::*,
    tasks::{AsyncComputeTaskPool, Task},
};
use futures_lite::future;

#[derive(Component)]


@@ 22,9 26,7 @@ pub struct GameCreateCall(Task<CreateResponse>);
pub struct GameCreateCallEvent;

async_task_start_call!(GameCreateCallEvent, GameCreateCall, |_ev, no| {
    let res = api::game::create(&no);

    res
    api::game::create(&no)
});

async_task_handle_call!(GameCreateCall, |response, _menu_data| {

M client/src/plugins/async_tasks/req_game_details.rs => client/src/plugins/async_tasks/req_game_details.rs +24 -19
@@ 7,12 7,16 @@
 */

use crate::{
    async_task_start_call, async_task_handle_call,
    api::{self, game::DetailsResponse}, NetworkingOptions,
    api::{self, game::DetailsResponse},
    async_task_handle_call, async_task_start_call,
    plugins::{game::GameData, ParseGameStatusEvent},
    NetworkingOptions,
};

use bevy::{prelude::*, tasks::{Task, AsyncComputeTaskPool}};
use bevy::{
    prelude::*,
    tasks::{AsyncComputeTaskPool, Task},
};
use futures_lite::future;

#[derive(Component)]


@@ 24,23 28,24 @@ pub struct GameDetailsCallEvent {
}

async_task_start_call!(GameDetailsCallEvent, GameDetailsCall, |ev, no| {
    let res = api::game::details(&no, &ev.game_id);

    res
    api::game::details(&no, &ev.game_id)
});

async_task_handle_call!(GameDetailsCall, |_unused, _ihatethis, response, game_data, parse_ev_w| {
    match response {
        Err(_err) => panic!("game details failed, handle me"),
        Ok(resp) => {
            info!("game details for {}", resp.id);
            game_data.game = Some(resp);

            // start data parsing
            game_data.parsing_data = true;
            parse_ev_w.send(ParseGameStatusEvent {
                game: game_data.game.as_ref().unwrap().clone(),
            });
async_task_handle_call!(
    GameDetailsCall,
    |_unused, _ihatethis, response, game_data, parse_ev_w| {
        match response {
            Err(_err) => panic!("game details failed, handle me"),
            Ok(resp) => {
                info!("game details for {}", resp.id);
                game_data.game = Some(resp);

                // start data parsing
                game_data.parsing_data = true;
                parse_ev_w.send(ParseGameStatusEvent {
                    game: game_data.game.as_ref().unwrap().clone(),
                });
            }
        }
    }
});
);

M client/src/plugins/async_tasks/req_game_forming.rs => client/src/plugins/async_tasks/req_game_forming.rs +9 -7
@@ 7,12 7,16 @@
 */

use crate::{
    async_task_start_call, async_task_handle_call,
    api::{self, game::FormingResponse}, NetworkingOptions,
    api::{self, game::FormingResponse},
    async_task_handle_call, async_task_start_call,
    plugins::menu::MenuData,
    NetworkingOptions,
};

use bevy::{prelude::*, tasks::{Task, AsyncComputeTaskPool}};
use bevy::{
    prelude::*,
    tasks::{AsyncComputeTaskPool, Task},
};
use futures_lite::future;

#[derive(Component)]


@@ 22,9 26,7 @@ pub struct GameFormingCall(Task<FormingResponse>);
pub struct GameFormingCallEvent;

async_task_start_call!(GameFormingCallEvent, GameFormingCall, |_ev, no| {
    let res = api::game::forming(&no);

    res
    api::game::forming(&no)
});

async_task_handle_call!(GameFormingCall, |response, menu_data| {


@@ 33,7 35,7 @@ async_task_handle_call!(GameFormingCall, |response, menu_data| {
        Ok(mut resp) => {
            menu_data.waiting = false;

            resp.sort_by_key(|g| g.created_at.clone());
            resp.sort_by_key(|g| g.created_at);
            resp.reverse();

            menu_data.forming_games = resp;

M client/src/plugins/async_tasks/req_game_join.rs => client/src/plugins/async_tasks/req_game_join.rs +8 -6
@@ 7,12 7,16 @@
 */

use crate::{
    async_task_start_call, async_task_handle_call,
    api::{self, game::JoinResponse}, NetworkingOptions,
    api::{self, game::JoinResponse},
    async_task_handle_call, async_task_start_call,
    plugins::menu::MenuData,
    NetworkingOptions,
};

use bevy::{prelude::*, tasks::{Task, AsyncComputeTaskPool}};
use bevy::{
    prelude::*,
    tasks::{AsyncComputeTaskPool, Task},
};
use futures_lite::future;

#[derive(Component)]


@@ 24,9 28,7 @@ pub struct GameJoinCallEvent {
}

async_task_start_call!(GameJoinCallEvent, GameJoinCall, |ev, no| {
    let res = api::game::join(&no, &ev.game_id);

    res
    api::game::join(&no, &ev.game_id)
});

async_task_handle_call!(GameJoinCall, |response, _menu_data| {

M client/src/plugins/async_tasks/req_game_mygames.rs => client/src/plugins/async_tasks/req_game_mygames.rs +9 -7
@@ 7,12 7,16 @@
 */

use crate::{
    async_task_start_call, async_task_handle_call,
    api::{self, game::MyGamesResponse}, NetworkingOptions,
    api::{self, game::MyGamesResponse},
    async_task_handle_call, async_task_start_call,
    plugins::menu::MenuData,
    NetworkingOptions,
};

use bevy::{prelude::*, tasks::{Task, AsyncComputeTaskPool}};
use bevy::{
    prelude::*,
    tasks::{AsyncComputeTaskPool, Task},
};
use futures_lite::future;

#[derive(Component)]


@@ 22,9 26,7 @@ pub struct GameMyGamesCall(Task<MyGamesResponse>);
pub struct GameMyGamesCallEvent;

async_task_start_call!(GameMyGamesCallEvent, GameMyGamesCall, |_ev, no| {
    let res = api::game::my_games(&no);

    res
    api::game::my_games(&no)
});

async_task_handle_call!(GameMyGamesCall, |response, menu_data| {


@@ 33,7 35,7 @@ async_task_handle_call!(GameMyGamesCall, |response, menu_data| {
        Ok(mut resp) => {
            menu_data.waiting = false;

            resp.sort_by_key(|g| g.created_at.clone());
            resp.sort_by_key(|g| g.created_at);
            resp.reverse();

            menu_data.my_games = resp;

M client/src/plugins/async_tasks/req_login.rs => client/src/plugins/async_tasks/req_login.rs +12 -7
@@ 7,12 7,19 @@
 */

use crate::{
    async_task_start_call, async_task_handle_call,
    api::{self, user::{LoginResponse, User}}, NetworkingOptions,
    plugins::{menu::MenuData, MenuUIState}, Global,
    api::{
        self,
        user::{LoginResponse, User},
    },
    async_task_handle_call, async_task_start_call,
    plugins::{menu::MenuData, MenuUIState},
    Global, NetworkingOptions,
};

use bevy::{prelude::*, tasks::{Task, AsyncComputeTaskPool}};
use bevy::{
    prelude::*,
    tasks::{AsyncComputeTaskPool, Task},
};
use futures_lite::future;

#[derive(Component)]


@@ 25,9 32,7 @@ pub struct LoginCallEvent {
}

async_task_start_call!(LoginCallEvent, LoginCall, |ev, no| {
    let res = api::user::login(&no, &ev.email, &ev.password);

    res
    api::user::login(&no, &ev.email, &ev.password)
});

async_task_handle_call!(LoginCall, |response, global, menu_data, no| {

M client/src/plugins/async_tasks/req_register.rs => client/src/plugins/async_tasks/req_register.rs +8 -6
@@ 7,12 7,16 @@
 */

use crate::{
    async_task_start_call, async_task_handle_call,
    api::{self, user::RegisterResponse}, NetworkingOptions,
    api::{self, user::RegisterResponse},
    async_task_handle_call, async_task_start_call,
    plugins::{menu::MenuData, MenuUIState},
    NetworkingOptions,
};

use bevy::{prelude::*, tasks::{Task, AsyncComputeTaskPool}};
use bevy::{
    prelude::*,
    tasks::{AsyncComputeTaskPool, Task},
};
use futures_lite::future;

#[derive(Component)]


@@ 26,9 30,7 @@ pub struct RegisterCallEvent {
}

async_task_start_call!(RegisterCallEvent, RegisterCall, |ev, no| {
    let res = api::user::register(&no, &ev.username, &ev.email, &ev.password);

    res
    api::user::register(&no, &ev.username, &ev.email, &ev.password)
});

async_task_handle_call!(RegisterCall, |response, menu_data| {

M client/src/plugins/game/card/mod.rs => client/src/plugins/game/card/mod.rs +8 -6
@@ 14,9 14,7 @@ use crate::game_status::Card;
pub struct CardPlugin;

impl Plugin for CardPlugin {
    fn build(&self, app: &mut App) {
        
    }
    fn build(&self, app: &mut App) {}
}

// NOTE: kind of like using enum variants


@@ 46,8 44,8 @@ impl Default for VisualCard {
                short_details: vec![],
                long_details: vec![],
                cost: 0,
                actions: vec![]
            }
                actions: vec![],
            },
        }
    }
}


@@ 66,7 64,11 @@ impl Default for VisualCardBundle {
        Self {
            visual_card: VisualCard::default(),
            transform: Transform {
                translation: Vec3 { x: 0., y: 0., z: 0. },
                translation: Vec3 {
                    x: 0.,
                    y: 0.,
                    z: 0.,
                },
                /// defaults to being rotated 90 degress on X, so flat on the
                /// table.
                rotation: Quat::from_euler(EulerRot::XYZ, -1.571, 0., 0.),

M client/src/plugins/game/mod.rs => client/src/plugins/game/mod.rs +9 -5
@@ 9,15 9,15 @@
use bevy::prelude::*;
use bevy_rapier3d::prelude::*;

use crate::{api::game::Game, Global, AppState, game_status::GameStatus};
use crate::{api::game::Game, game_status::GameStatus, AppState, Global};

use self::card::VisualCardBundle;

use super::GameDetailsCallEvent;

mod ui;
mod card;
mod supply;
mod ui;

pub struct GamePlugin;



@@ 66,10 66,14 @@ fn game_setup(

    commands.spawn(VisualCardBundle {
        transform: Transform {
                translation: Vec3 { x: 0., y: 3., z: 0. },
                rotation: Quat::from_euler(EulerRot::XYZ, -90., 0., 0.),
                ..Default::default()
            translation: Vec3 {
                x: 0.,
                y: 3.,
                z: 0.,
            },
            rotation: Quat::from_euler(EulerRot::XYZ, -90., 0., 0.),
            ..Default::default()
        },
        ..Default::default()
    });
}

M client/src/plugins/game/supply/mod.rs => client/src/plugins/game/supply/mod.rs +14 -10
@@ 9,7 9,10 @@
use bevy::prelude::*;
use bevy_rapier3d::prelude::*;

use super::{GameData, card::{VisualCardBundle, VisualCard, visual_card_kind}};
use super::{
    card::{visual_card_kind, VisualCard, VisualCardBundle},
    GameData,
};

pub struct SupplyPlugin;



@@ 35,13 38,15 @@ fn spawn_supply_piles(
    };

    for (index, pile) in status.supply_piles.iter().enumerate() {
        commands.spawn(VisualCardBundle {
            visual_card: VisualCard {
                card: pile.card.clone(),
            },
            rigid_body: RigidBody::Fixed,
            ..Default::default()
        }).insert(visual_card_kind::Supply(index));
        commands
            .spawn(VisualCardBundle {
                visual_card: VisualCard {
                    card: pile.card.clone(),
                },
                rigid_body: RigidBody::Fixed,
                ..Default::default()
            })
            .insert(visual_card_kind::Supply(index));
    }

    psp_ev_w.send(PositionSupplyPilesEvent);


@@ 52,8 57,7 @@ pub struct PositionSupplyPilesEvent;
fn position_supply_piles(
    mut pile_query: Query<(&VisualCard, &mut Transform), With<visual_card_kind::Supply>>,
) {
    let mut piles: Vec<(&VisualCard, Mut<Transform>)> = pile_query.iter_mut()
        .collect::<Vec<_>>();
    let mut piles: Vec<(&VisualCard, Mut<Transform>)> = pile_query.iter_mut().collect::<Vec<_>>();

    piles.sort_by_key(|(vc, _t)| vc.card.name.clone());
    piles.sort_by_key(|(vc, _t)| vc.card.cost);

M client/src/plugins/game/ui/mod.rs => client/src/plugins/game/ui/mod.rs +136 -133
@@ 9,9 9,14 @@
use bevy::prelude::*;
use bevy_egui::{egui, EguiContexts};

use crate::{plugins::{GameDetailsCallEvent, GameActionCreateCallEvent}, Global, api::game::{Action, Command, Game}, game_status::{Card, PlayerState}, AppState};
use crate::{
    api::game::{Action, Command, Game},
    game_status::{Card, PlayerState},
    plugins::{GameActionCreateCallEvent, GameDetailsCallEvent},
    AppState, Global,
};

use super::{GameData, supply::SpawnSupplyPilesEvent};
use super::{supply::SpawnSupplyPilesEvent, GameData};

pub struct GameUIPlugin;



@@ 29,13 34,12 @@ pub fn details_ui(
    mut create_action_ev_w: EventWriter<GameActionCreateCallEvent>,
    mut ssp_ev_w: EventWriter<SpawnSupplyPilesEvent>,
) {
    egui::Window::new("Game Details")
        .show(contexts.ctx_mut(), |ui| {
            let Some(game) = &game_data.game else {
    egui::Window::new("Game Details").show(contexts.ctx_mut(), |ui| {
        let Some(game) = &game_data.game else {
                // early return if game is None
                return;
            };
            let Some(status) = &game_data.game_status else {
        let Some(status) = &game_data.game_status else {
                if game_data.parsing_data {
                    // early return if game_status is None, and we're parsing it
                    return;


@@ 45,157 49,156 @@ pub fn details_ui(
                return;
            };

            if ui.button("Refresh").clicked() {
                details_ev_w.send(GameDetailsCallEvent {
                    game_id: game_data.game.as_ref().unwrap().id.clone(),
                });
            }
        if ui.button("Refresh").clicked() {
            details_ev_w.send(GameDetailsCallEvent {
                game_id: game_data.game.as_ref().unwrap().id.clone(),
            });
        }

            if status.actions.is_empty() && game.host_id == global.user.as_ref().unwrap().id {
                if ui.button("Init Game").clicked() {
                    // NOTE/FIXME: hardcoded game init
                    hardcoded_init(&game, &mut create_action_ev_w);
                }
        if status.actions.is_empty() && game.host_id == global.user.as_ref().unwrap().id {
            if ui.button("Init Game").clicked() {
                // NOTE/FIXME: hardcoded game init
                hardcoded_init(game, &mut create_action_ev_w);
            }
        }

            if ui.button("spawn things").clicked() {
                ssp_ev_w.send(SpawnSupplyPilesEvent);
            }
        if ui.button("spawn things").clicked() {
            ssp_ev_w.send(SpawnSupplyPilesEvent);
        }

        ui.separator();

        egui::CollapsingHeader::new("Game")
            .default_open(true)
            .show(ui, |ui| {
                ui.label(format!("Host: {}", game.host.as_ref().unwrap().username));
                ui.label(format!("Guest: {}", game.guest.as_ref().unwrap().username));

            ui.separator();

            egui::CollapsingHeader::new("Game")
                .default_open(true)
                .show(ui, |ui| {
                    ui.label(format!("Host: {}", game.host.as_ref().unwrap().username));
                    ui.label(format!("Guest: {}", game.guest.as_ref().unwrap().username));

                    ui.label(format!("State: {:?}", game.state));
                });

            egui::CollapsingHeader::new("Supply Piles")
                .default_open(false)
                .show(ui, |ui| {
                    for pile in &status.supply_piles {
                        egui::CollapsingHeader::new(&pile.card.name)
                            .default_open(true)
                            .show(ui, |ui| {
                                ui.label(format!("Amount: {}", pile.amount));
                            });
                    }
                });

            egui::CollapsingHeader::new("Log")
                .default_open(false)
                .show(ui, |ui| {
                    egui::ScrollArea::vertical()
                        .max_width(f32::INFINITY)
                ui.label(format!("State: {:?}", game.state));
            });

        egui::CollapsingHeader::new("Supply Piles")
            .default_open(false)
            .show(ui, |ui| {
                for pile in &status.supply_piles {
                    egui::CollapsingHeader::new(&pile.card.name)
                        .default_open(true)
                        .show(ui, |ui| {
                            for (i, action) in status.actions.iter().enumerate() {
                                egui::CollapsingHeader::new(format!("{}", i))
                                    .default_open(false)
                                    .show(ui, |ui| {
                                        ui.add(
                                            egui::TextEdit::multiline(&mut serde_json::to_string_pretty(action).unwrap())
                                            .code_editor()
                                            .interactive(false)
                                            .desired_width(f32::INFINITY)
                                            );
                                    });
                            }
                            ui.label(format!("Amount: {}", pile.amount));
                        });
                });
        });
                }
            });

        egui::CollapsingHeader::new("Log")
            .default_open(false)
            .show(ui, |ui| {
                egui::ScrollArea::vertical()
                    .max_width(f32::INFINITY)
                    .show(ui, |ui| {
                        for (i, action) in status.actions.iter().enumerate() {
                            egui::CollapsingHeader::new(format!("{}", i))
                                .default_open(false)
                                .show(ui, |ui| {
                                    ui.add(
                                        egui::TextEdit::multiline(
                                            &mut serde_json::to_string_pretty(action).unwrap(),
                                        )
                                        .code_editor()
                                        .interactive(false)
                                        .desired_width(f32::INFINITY),
                                    );
                                });
                        }
                    });
            });
    });
}

fn hardcoded_init(
    game: &Game,
    create_action_ev_w: &mut EventWriter<GameActionCreateCallEvent>,
) {
fn hardcoded_init(game: &Game, create_action_ev_w: &mut EventWriter<GameActionCreateCallEvent>) {
    // first, piles
    create_action_ev_w.send(GameActionCreateCallEvent {
        action: Action::new(
                    &game.id,
                    &game.host_id,
                    &game.host_id,
                    &Command::InitSupplyPile {
                        card: Card {
                            name: "Test Card".to_string(),
                            short_details: vec![],
                            long_details: vec![],
                            cost: 4,
                            actions: vec![]
                        },
                        amount: 10
                    },
                    fastrand::u64(u64::MIN..=u64::MAX)
                    ),
            &game.id,
            &game.host_id,
            &game.host_id,
            &Command::InitSupplyPile {
                card: Card {
                    name: "Test Card".to_string(),
                    short_details: vec![],
                    long_details: vec![],
                    cost: 4,
                    actions: vec![],
                },
                amount: 10,
            },
            fastrand::u64(u64::MIN..=u64::MAX),
        ),
    });
    create_action_ev_w.send(GameActionCreateCallEvent {
        action: Action::new(
                    &game.id,
                    &game.host_id,
                    &game.host_id,
                    &Command::InitSupplyPile {
                        card: Card {
                            name: "Test Card 2".to_string(),
                            short_details: vec![],
                            long_details: vec![],
                            cost: 4,
                            actions: vec![]
                        },
                        amount: 10
                    },
                    fastrand::u64(u64::MIN..=u64::MAX)
                    ),
            &game.id,
            &game.host_id,
            &game.host_id,
            &Command::InitSupplyPile {
                card: Card {
                    name: "Test Card 2".to_string(),
                    short_details: vec![],
                    long_details: vec![],
                    cost: 4,
                    actions: vec![],
                },
                amount: 10,
            },
            fastrand::u64(u64::MIN..=u64::MAX),
        ),
    });
    create_action_ev_w.send(GameActionCreateCallEvent {
        action: Action::new(
                    &game.id,
                    &game.host_id,
                    &game.host_id,
                    &Command::InitSupplyPile {
                        card: Card {
                            name: "Test Card 3".to_string(),
                            short_details: vec![],
                            long_details: vec![],
                            cost: 4,
                            actions: vec![]
                        },
                        amount: 10
                    },
                    fastrand::u64(u64::MIN..=u64::MAX)
                    ),
            &game.id,
            &game.host_id,
            &game.host_id,
            &Command::InitSupplyPile {
                card: Card {
                    name: "Test Card 3".to_string(),
                    short_details: vec![],
                    long_details: vec![],
                    cost: 4,
                    actions: vec![],
                },
                amount: 10,
            },
            fastrand::u64(u64::MIN..=u64::MAX),
        ),
    });
    create_action_ev_w.send(GameActionCreateCallEvent {
        action: Action::new(
                    &game.id,
                    &game.host_id,
                    &game.host_id,
                    &Command::InitSupplyPile {
                        card: Card {
                            name: "Test Card 4".to_string(),
                            short_details: vec![],
                            long_details: vec![],
                            cost: 4,
                            actions: vec![]
                        },
                        amount: 10
                    },
                    fastrand::u64(u64::MIN..=u64::MAX)
                    ),
            &game.id,
            &game.host_id,
            &game.host_id,
            &Command::InitSupplyPile {
                card: Card {
                    name: "Test Card 4".to_string(),
                    short_details: vec![],
                    long_details: vec![],
                    cost: 4,
                    actions: vec![],
                },
                amount: 10,
            },
            fastrand::u64(u64::MIN..=u64::MAX),
        ),
    });

    // second, set a player to the action phase, to start the game
    create_action_ev_w.send(GameActionCreateCallEvent {
        action: Action::new(
                    &game.id,
                    &game.host_id,
                    &game.host_id,
                    &Command::ChangePlayerState {
                        state: PlayerState::ActionPhase,
                    },
                    fastrand::u64(u64::MIN..=u64::MAX)
                    ),
            &game.id,
            &game.host_id,
            &game.host_id,
            &Command::ChangePlayerState {
                state: PlayerState::ActionPhase,
            },
            fastrand::u64(u64::MIN..=u64::MAX),
        ),
    });
}

M client/src/plugins/menu/mod.rs => client/src/plugins/menu/mod.rs +1 -2
@@ 8,7 8,7 @@

use bevy::prelude::*;

use crate::{AppState, api::game::Game};
use crate::{api::game::Game, AppState};

mod ui;



@@ 83,7 83,6 @@ pub enum MenuUIState {
    Register,
    Main,
    Browse,

}

#[derive(Debug, PartialEq)]

M client/src/plugins/menu/ui/browse.rs => client/src/plugins/menu/ui/browse.rs +38 -38
@@ 9,18 9,21 @@
use bevy::prelude::*;
use bevy_egui::egui;

use crate::{plugins::{
    GameCreateCallEvent, BrowseState, 
    GameFormingCallEvent, GameMyGamesCallEvent,
    GameJoinCallEvent, menu::MenuState
}, api::game::{GameState, Game}, Global, AppState};
use crate::{
    api::game::{Game, GameState},
    plugins::{
        menu::MenuState, BrowseState, GameCreateCallEvent, GameFormingCallEvent, GameJoinCallEvent,
        GameMyGamesCallEvent,
    },
    AppState, Global,
};

use super::{MenuData, MenuUIState};

pub fn view(
    mut commands: &mut Commands,
    commands: &mut Commands,
    ui: &mut egui::Ui,
    mut global: &mut Global,
    global: &mut Global,
    data: &mut MenuData,
    create_ev_w: &mut EventWriter<GameCreateCallEvent>,
    forming_ev_w: &mut EventWriter<GameFormingCallEvent>,


@@ 46,33 49,34 @@ pub fn view(
        .show_inside(ui, |ui| {
            ui.horizontal(|ui| {
                ui.selectable_value(&mut data.browse_state, BrowseState::Forming, "Forming");
                ui.selectable_value(&mut data.browse_state, BrowseState::InProgress, "In Progress");
                ui.selectable_value(
                    &mut data.browse_state,
                    BrowseState::InProgress,
                    "In Progress",
                );
                ui.selectable_value(&mut data.browse_state, BrowseState::Finished, "Finished");

                ui.with_layout(
                    egui::Layout::right_to_left(egui::Align::Center),
                    |ui| {
                        if ui.button("Refresh").clicked() {
                            match data.browse_state {
                                BrowseState::Forming => {
                                    if !data.waiting {
                                        forming_ev_w.send(GameFormingCallEvent);
                                    }
                ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
                    if ui.button("Refresh").clicked() {
                        match data.browse_state {
                            BrowseState::Forming => {
                                if !data.waiting {
                                    forming_ev_w.send(GameFormingCallEvent);
                                }
                                BrowseState::InProgress => {
                                    if !data.waiting {
                                        mygames_ev_w.send(GameMyGamesCallEvent);
                                    }
                            }
                            BrowseState::InProgress => {
                                if !data.waiting {
                                    mygames_ev_w.send(GameMyGamesCallEvent);
                                }
                                BrowseState::Finished => {
                                    if !data.waiting {
                                        mygames_ev_w.send(GameMyGamesCallEvent);
                                    }
                            }
                            BrowseState::Finished => {
                                if !data.waiting {
                                    mygames_ev_w.send(GameMyGamesCallEvent);
                                }
                            }
                        }
                    },
                );
                    }
                });
            });
        });
    ui.vertical_centered(|ui| {


@@ 96,7 100,7 @@ pub fn view(
                for game in &games {
                    game_row(ui, game, |ui| {
                        if ui.button("Enter").clicked() {
                            enter_game(&mut commands, &mut global, &game.id);
                            enter_game(commands, global, &game.id);
                        }
                    });
                }


@@ 115,8 119,7 @@ pub fn view(
                        });
                }
            }
        }
        );
        });
    });
}



@@ 128,17 131,14 @@ fn game_row(ui: &mut egui::Ui, game: &Game, buttons: impl FnOnce(&mut egui::Ui))
        .inner_margin(4.)
        .show(ui, |ui| {
            ui.horizontal(|ui| {
                ui.label(format!(
                        "Host: {}",
                        game.host.as_ref().unwrap().username
                        ));
                ui.label(format!("Host: {}", game.host.as_ref().unwrap().username));
                ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
                    buttons(ui);

                    ui.label(format!(
                            "{}",
                            chrono_humanize::HumanTime::from(game.created_at)
                            ));
                        "{}",
                        chrono_humanize::HumanTime::from(game.created_at)
                    ));
                });
            });
        });


@@ 146,7 146,7 @@ fn game_row(ui: &mut egui::Ui, game: &Game, buttons: impl FnOnce(&mut egui::Ui))

fn enter_game(commands: &mut Commands, global: &mut Global, game_id: &str) {
    global.cur_game_id = game_id.to_string();
    

    commands.insert_resource(NextState(Some(MenuState::None)));
    commands.insert_resource(NextState(Some(AppState::InGame)));
}

M client/src/plugins/menu/ui/login.rs => client/src/plugins/menu/ui/login.rs +2 -6
@@ 9,15 9,11 @@
use bevy::prelude::*;
use bevy_egui::egui;

use crate::{util::egui::password, plugins::LoginCallEvent};
use crate::{plugins::LoginCallEvent, util::egui::password};

use super::{MenuData, MenuUIState};

pub fn view(
    ui: &mut egui::Ui,
    data: &mut MenuData,
    login_ev_w: &mut EventWriter<LoginCallEvent>,
) {
pub fn view(ui: &mut egui::Ui, data: &mut MenuData, login_ev_w: &mut EventWriter<LoginCallEvent>) {
    ui.horizontal(|ui| {
        ui.label("Email:");
        ui.text_edit_singleline(&mut data.login_email)

M client/src/plugins/menu/ui/mod.rs => client/src/plugins/menu/ui/mod.rs +10 -6
@@ 7,9 7,15 @@
 */

use bevy::prelude::*;
use bevy_egui::{EguiContexts, egui};
use bevy_egui::{egui, EguiContexts};

use crate::{plugins::{LoginCallEvent, RegisterCallEvent, GameCreateCallEvent, GameFormingCallEvent, GameMyGamesCallEvent, GameJoinCallEvent}, Global};
use crate::{
    plugins::{
        GameCreateCallEvent, GameFormingCallEvent, GameJoinCallEvent, GameMyGamesCallEvent,
        LoginCallEvent, RegisterCallEvent,
    },
    Global,
};

pub use super::{MenuData, MenuUIState};



@@ 34,8 40,7 @@ pub fn ui(
        .collapsible(false)
        .resizable(false)
        .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
        .show(contexts.ctx_mut(), |ui| {
        match &data.ui_state {
        .show(contexts.ctx_mut(), |ui| match &data.ui_state {
            MenuUIState::Loading => {
                ui.horizontal(|ui| {
                    ui.spinner();


@@ 59,6 64,5 @@ pub fn ui(
                &mut mygames_ev_w,
                &mut join_ev_w,
            ),
        }
    });
        });
}

M client/src/plugins/menu/ui/register.rs => client/src/plugins/menu/ui/register.rs +18 -15
@@ 9,7 9,7 @@
use bevy::prelude::*;
use bevy_egui::egui;

use crate::{util::egui::password, plugins::RegisterCallEvent};
use crate::{plugins::RegisterCallEvent, util::egui::password};

use super::{MenuData, MenuUIState};



@@ 39,20 39,23 @@ pub fn view(
        ui.label(egui::RichText::new(&data.error).color(egui::Color32::RED));
    }

    ui.add_enabled_ui(data.register_password == data.register_password_confirm, |ui| {
        ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
            if ui.button("Register").clicked() {
                data.error.clear();
                data.ui_state = MenuUIState::Loading;

                register_ev_w.send(RegisterCallEvent {
                    username: data.register_username.clone(),
                    email: data.register_email.clone(),
                    password: data.register_password.clone(),
                });
            }
        });
    });
    ui.add_enabled_ui(
        data.register_password == data.register_password_confirm,
        |ui| {
            ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
                if ui.button("Register").clicked() {
                    data.error.clear();
                    data.ui_state = MenuUIState::Loading;

                    register_ev_w.send(RegisterCallEvent {
                        username: data.register_username.clone(),
                        email: data.register_email.clone(),
                        password: data.register_password.clone(),
                    });
                }
            });
        },
    );

    ui.vertical_centered(|ui| {
        ui.label("I have an account:");